diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..48edc26187 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,21 @@ +# build config +/.scrutinizer.yml export-ignore +/.github export-ignore +/php_cs.dist export-ignore +/phpmd.xml.dist export-ignore +/phpstan.neon export-ignore + +/composer.lock export-ignore + +# git files +/.gitignore export-ignore +/.gitattributes export-ignore + +# project directories +/build export-ignore +/docs export-ignore +/samples export-ignore + +# tests +/phpunit.xml.dist export-ignore +/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/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..b91f1bd4ac --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ +### Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. + +Fixes # (issue) + +### Checklist: + +- [ ] 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.5.0.md) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..68ac78ad4c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: +- package-ecosystem: composer + directory: "/" + schedule: + interval: monthly + time: "11:00" + open-pull-requests-limit: 10 diff --git a/.github/support.yml b/.github/support.yml new file mode 100644 index 0000000000..c1b039ccac --- /dev/null +++ b/.github/support.yml @@ -0,0 +1,15 @@ +# Label used to mark issues as support requests +supportLabel: Question +# Comment to post on issues marked as support requests. Add a link +# to a support page, or set to `false` to disable +supportComment: > + This looks like a support question. Please ask your support questions on + [StackOverflow](http://stackoverflow.com/questions/tagged/phpword), + or [Gitter](https://gitter.im/PHPOffice/PHPWord). + + Thank you for your contributions. + +# Whether to close issues marked as support requests +close: true +# Whether to lock issues marked as support requests +lock: false 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/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000000..0e2e9ea96d --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,124 @@ +name: PHPWord +on: [push, pull_request] +jobs: + php-cs-fixer: + name: PHP CS Fixer + runs-on: ubuntu-latest + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: mbstring, intl, gd, xml, dom, json, fileinfo, curl, zip, iconv + - uses: actions/checkout@v2 + + - name: Validate composer config + run: composer validate --strict + + - name: Composer Install + run: composer global require friendsofphp/php-cs-fixer + + - name: Add environment path + run: export PATH="$PATH:$HOME/.composer/vendor/bin" + + - name: Run PHPCSFixer + run: php-cs-fixer fix --dry-run --diff + + phpmd: + name: PHP Mess Detector + runs-on: ubuntu-latest + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: gd, xml, zip + - uses: actions/checkout@v2 + + - name: Composer Install + run: composer install --ansi --prefer-dist --no-interaction --no-progress + + - name: Run phpmd + run: ./vendor/bin/phpmd src/,tests/ text ./phpmd.xml.dist --exclude "src/PhpWord/Shared/PCLZip/*" + + phpstan: + name: PHP Static Analysis + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Disabled PHPStan in '7.1' + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: gd, xml, zip + - uses: actions/checkout@v2 + + - name: Composer Install + run: composer install --ansi --prefer-dist --no-interaction --no-progress + + - name: Run phpstan + run: ./vendor/bin/phpstan analyse -c phpstan.neon.dist + + phpunit: + name: PHPUnit ${{ matrix.php }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: gd, xml, zip + coverage: ${{ (matrix.php == '7.3') && 'xdebug' || 'none' }} + + - uses: actions/checkout@v2 + + - name: Composer Install + run: composer install --ansi --prefer-dist --no-interaction --no-progress + + - name: Run phpunit + if: matrix.php != '7.3' + run: ./vendor/bin/phpunit -c phpunit.xml.dist --no-coverage + + - name: Run phpunit + if: matrix.php == '7.3' + run: ./vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover build/clover.xml + + - name: Upload coverage results to Coveralls + if: matrix.php == '7.3' + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar + chmod +x php-coveralls.phar + php php-coveralls.phar --coverage_clover=build/clover.xml --json_path=build/coveralls-upload.json -vvv + + samples: + name: Check samples + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: gd, xml, zip + coverage: xdebug + + - uses: actions/checkout@v2 + + - name: Composer Install + run: composer install --ansi --prefer-dist --no-interaction --no-progress + + - name: Generate samples files + run: composer run samples diff --git a/.github_changelog_generator b/.github_changelog_generator new file mode 100644 index 0000000000..787995f82f --- /dev/null +++ b/.github_changelog_generator @@ -0,0 +1,8 @@ +user=PHPOffice +project=PHPWord + +since-tag=0.18.1 +future-release=0.18.2 + +issues=false +pulls=true diff --git a/.gitignore b/.gitignore index e5deb64387..6918df72e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +/composer.lock + + .DS_Store ._* .Spotlight-V100 @@ -6,14 +9,20 @@ Thumbs.db Desktop.ini .idea _build +/build phpunit.xml -composer.lock composer.phar +composer.lock vendor /report -/samples/resources +/build /samples/results /.settings phpword.ini /.buildpath -/.project \ No newline at end of file +/.scannerwork +/.project +/nbproject +/.php_cs.cache +/.phpunit.result.cache +/public \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000000..cd46cac83e --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,227 @@ +notName('pclzip.lib.php') + ->notName('OLERead.php') + ->in('samples') + ->in('src') + ->in('tests'); + +$config = new PhpCsFixer\Config(); +$config + ->setRiskyAllowed(true) + ->setFinder($finder) + ->setCacheFile(sys_get_temp_dir() . '/php-cs-fixer' . preg_replace('~\W~', '-', __DIR__)) + ->setRules([ + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'backtick_to_shell_exec' => true, + 'binary_operator_spaces' => true, + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => true, + 'braces' => true, + 'cast_spaces' => true, + 'class_attributes_separation' => ['elements' => ['method' => 'one', 'property' => 'one']], // const are often grouped with other related const + 'class_definition' => false, + 'class_keyword_remove' => false, // ::class keyword gives us better support in IDE + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'combine_nested_dirname' => true, + 'comment_to_phpdoc' => false, // interferes with annotations + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'constant_case' => true, + 'date_time_immutable' => false, // Break our unit tests + 'declare_equal_normalize' => true, + 'declare_strict_types' => false, // Too early to adopt strict types + 'dir_constant' => true, + 'doctrine_annotation_array_assignment' => true, + 'doctrine_annotation_braces' => true, + 'doctrine_annotation_indentation' => true, + 'doctrine_annotation_spaces' => true, + 'elseif' => true, + 'encoding' => true, + 'ereg_to_preg' => true, + 'escape_implicit_backslashes' => true, + 'explicit_indirect_variable' => false, // I feel it makes the code actually harder to read + 'explicit_string_variable' => false, // I feel it makes the code actually harder to read + 'final_class' => false, // We need non-final classes + 'final_internal_class' => true, + 'final_public_method_for_abstract_class' => false, // We need non-final methods + 'fopen_flag_order' => true, + 'fopen_flags' => true, + 'full_opening_tag' => true, + 'fully_qualified_strict_types' => true, + 'function_declaration' => true, + 'function_to_constant' => true, + 'function_typehint_space' => true, + 'general_phpdoc_annotation_remove' => ['annotations' => ['access', 'category', 'copyright', 'throws']], + 'global_namespace_import' => true, + 'header_comment' => false, // We don't use common header in all our files + 'heredoc_indentation' => false, // Requires PHP >= 7.3 + 'heredoc_to_nowdoc' => false, // Not sure about this one + 'implode_call' => true, + 'include' => true, + 'increment_style' => true, + 'indentation_type' => true, + 'is_null' => true, + 'line_ending' => true, + 'linebreak_after_opening_tag' => true, + 'list_syntax' => ['syntax' => 'short'], + 'logical_operators' => true, + 'lowercase_cast' => true, + 'lowercase_keywords' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'mb_str_functions' => false, // No, too dangerous to change that + 'method_argument_space' => true, + 'method_chaining_indentation' => true, + 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => true, + 'native_constant_invocation' => false, // Micro optimization that look messy + 'native_function_casing' => true, + 'native_function_invocation' => false, // I suppose this would be best, but I am still unconvinced about the visual aspect of it + 'native_function_type_declaration_casing' => true, + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_alternative_syntax' => true, + 'no_binary_string' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_blank_lines_before_namespace' => false, // we want 1 blank line before namespace + 'no_break_comment' => true, + 'no_closing_tag' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_homoglyph_names' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => true, + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_null_property_initialization' => true, + 'no_php4_constructor' => true, + 'no_short_bool_cast' => true, + 'echo_tag_syntax' => ['format' => 'long'], + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_around_offset' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_superfluous_elseif' => false, // Might be risky on a huge code base + 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true], + 'no_trailing_comma_in_list_call' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_curly_braces' => true, + 'no_unneeded_final_method' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unset_cast' => true, + 'no_unset_on_property' => true, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => true, + 'normalize_index_brace' => true, + 'not_operator_with_space' => false, // No we prefer to keep '!' without spaces + 'not_operator_with_successor_space' => false, // idem + 'nullable_type_declaration_for_default_null_value' => true, + 'object_operator_without_whitespace' => true, + 'ordered_class_elements' => false, // We prefer to keep some freedom + 'ordered_imports' => true, + 'ordered_interfaces' => true, + 'php_unit_construct' => true, + 'php_unit_dedicate_assert' => true, + 'php_unit_dedicate_assert_internal_type' => true, + 'php_unit_expectation' => true, + 'php_unit_fqcn_annotation' => true, + 'php_unit_internal_class' => false, // Because tests are excluded from package + 'php_unit_method_casing' => true, + 'php_unit_mock' => true, + 'php_unit_mock_short_will_return' => true, + 'php_unit_namespaced' => true, + 'php_unit_no_expectation_annotation' => true, + 'phpdoc_order_by_value' => ['annotations' => ['covers']], + 'php_unit_set_up_tear_down_visibility' => true, + 'php_unit_size_class' => false, // That seems extra work to maintain for little benefits + 'php_unit_strict' => false, // We sometime actually need assertEquals + 'php_unit_test_annotation' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'php_unit_test_class_requires_covers' => false, // We don't care as much as we should about coverage + 'phpdoc_add_missing_param_annotation' => false, // Don't add things that bring no value + 'phpdoc_align' => false, // Waste of time + 'phpdoc_annotation_without_dot' => true, + 'phpdoc_indent' => true, + //'phpdoc_inline_tag' => true, + 'phpdoc_line_span' => false, // Unfortunately our old comments turn even uglier with this + 'phpdoc_no_access' => true, + 'phpdoc_no_alias_tag' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + 'phpdoc_to_comment' => false, // interferes with annotations + 'phpdoc_to_param_type' => false, // Because experimental, but interesting for one shot use + 'phpdoc_to_return_type' => false, // idem + 'phpdoc_trim' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => true, + 'phpdoc_var_annotation_correct_order' => true, + 'phpdoc_var_without_name' => true, + 'pow_to_exponentiation' => true, + 'protected_to_private' => true, + 'psr_autoloading' => true, + 'random_api_migration' => true, + 'return_assignment' => false, // Sometimes useful for clarity or debug + 'return_type_declaration' => true, + 'self_accessor' => true, + 'self_static_accessor' => true, + 'semicolon_after_instruction' => false, // Buggy in `samples/index.php` + 'set_type_to_cast' => true, + 'short_scalar_cast' => true, + 'simple_to_complex_string_variable' => false, // Would differ from TypeScript without obvious advantages + 'simplified_null_return' => false, // Even if technically correct we prefer to be explicit + 'single_blank_line_at_eof' => true, + 'single_blank_line_before_namespace' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'single_line_comment_style' => true, + 'single_line_throw' => false, // I don't see any reason for having a special case for Exception + 'single_quote' => true, + 'single_trait_insert_per_statement' => true, + 'space_after_semicolon' => true, + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'static_lambda' => false, // Risky if we can't guarantee nobody use `bindTo()` + 'strict_comparison' => false, // No, too dangerous to change that + 'strict_param' => false, // No, too dangerous to change that + 'string_line_ending' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'ternary_operator_spaces' => true, + 'ternary_to_null_coalescing' => true, + 'trailing_comma_in_multiline' => true, + 'trim_array_spaces' => true, + 'unary_operator_spaces' => true, + 'visibility_required' => ['elements' => ['property', 'method']], // not const + 'void_return' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => false, + ]); + +return $config; diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 6f982d8e89..0000000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,24 +0,0 @@ -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: true - timeout: 900 - 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/.travis.yml b/.travis.yml deleted file mode 100644 index 22f45d9b2e..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,49 +0,0 @@ -language: php - -php: - - 5.3 - - 5.4 - - 5.5 - - 5.6 - - hhvm - -matrix: - allow_failures: - - php: 5.2 - - php: hhvm - -env: - global: - - secure: "Sq+6bVtnPsu0mWX8DWQ+9bGAjxMcGorksUiHc4YIXEJsuDfVmVlH8tTD547IeCjDAx9MxXerZ2Z4HSjxTB70VEnJPvZMHI/EZn4Ny31YLHEthdZbV5Gd1h0TGp8VOzPKGShvGrtGBX6MvMfgpK4zuieVWbSfdKeecm8ZNLMpUd4=" - -before_script: - ## Packages - - sudo apt-get -qq update > /dev/null - - sudo apt-get -qq install graphviz > /dev/null - ## Composer - - composer self-update - - composer install --prefer-source --dev - ## PHPDocumentor - - mkdir -p build/docs - - mkdir -p build/coverage - -script: - ## PHP_CodeSniffer - - ./vendor/bin/phpcs src/ tests/ --standard=PSR2 -n --ignore=src/PhpWord/Shared/PCLZip - ## PHP Copy/Paste Detector - - ./vendor/bin/phpcpd src/ tests/ --verbose - ## PHP Mess Detector - - ./vendor/bin/phpmd src/,tests/ text ./phpmd.xml.dist --exclude pclzip.lib.php - ## PHPUnit - - ./vendor/bin/phpunit -c ./ --coverage-text --coverage-html ./build/coverage - ## PHPLOC - - ./vendor/bin/phploc src/ - ## PHPDocumentor - - ./vendor/bin/phpdoc -q -d ./src -t ./build/docs --ignore "*/src/PhpWord/Shared/*/*" --template="responsive-twig" - -after_script: - ## PHPDocumentor - - bash .travis_shell_after_success.sh - ## Scrutinizer - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml diff --git a/.travis_shell_after_success.sh b/.travis_shell_after_success.sh deleted file mode 100644 index 35c7a338b9..0000000000 --- a/.travis_shell_after_success.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -echo "--DEBUG--" -echo "TRAVIS_REPO_SLUG: $TRAVIS_REPO_SLUG" -echo "TRAVIS_PHP_VERSION: $TRAVIS_PHP_VERSION" -echo "TRAVIS_PULL_REQUEST: $TRAVIS_PULL_REQUEST" - -if [ "$TRAVIS_REPO_SLUG" == "PHPOffice/PHPWord" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_PHP_VERSION" == "5.5" ]; then - - echo -e "Publishing PHPDoc...\n" - - cp -R build/docs $HOME/docs-latest - cp -R build/coverage $HOME/coverage-latest - - cd $HOME - git config --global user.email "travis@travis-ci.org" - git config --global user.name "travis-ci" - git clone --quiet --branch=gh-pages https://${GH_TOKEN}@github.com/PHPOffice/PHPWord gh-pages > /dev/null - - cd gh-pages - echo "--DEBUG : Suppression" - git rm -rf ./docs/$TRAVIS_BRANCH - - echo "--DEBUG : Dossier" - mkdir -p docs/$TRAVIS_BRANCH - mkdir -p coverage/$TRAVIS_BRANCH - - echo "--DEBUG : Copie" - cp -Rf $HOME/docs-latest/* ./docs/$TRAVIS_BRANCH/ - cp -Rf $HOME/coverage-latest/* ./coverage/$TRAVIS_BRANCH/ - - echo "--DEBUG : Git" - git add -f . - git commit -m "PHPDocumentor (Travis Build: $TRAVIS_BUILD_NUMBER - Branch: $TRAVIS_BRANCH)" - git push -fq origin gh-pages > /dev/null - - echo -e "Published PHPDoc to gh-pages.\n" - -fi diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 93ab0127a9..0000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,343 +0,0 @@ -# Changelog - -This is the changelog between releases of PHPWord. Releases are listed in reverse chronological order with the latest version listed on top, while additions/changes in each release are listed in chronological order. Changes in each release are divided into three parts: added or change features, bugfixes, and miscellaneous improvements. Each line contains short information about the change made, the person who made it, and the related issue number(s) in GitHub. - -## 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 - -## 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 - -## 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 - - -## 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 - -## 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 - -## 0.9.1 - 27 Mar 2014 - -This is a bugfix release for PSR-4 compatibility. - -- Fixed PSR-4 composer autoloader - @AntonTyutin - -## 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/) - @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 - -## 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 - -## 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 - -## 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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 335ad2d519..ac262a0f34 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,30 @@ # Contributing to PHPWord -PHPWord is built by the crowd and for the crowd. Every contribution is welcome; either by [submitting](https://github.com/PHPOffice/PHPWord/issues) bug issues or suggesting improvements, or in a more active form like [requesting](https://github.com/PHPOffice/PHPWord/pulls) a pull. +PHPWord is built by the crowd and for the crowd. Every contribution is welcome; either by [reporting a bug](https://github.com/PHPOffice/PHPWord/issues/new?labels=Bug+Report&template=bug_report.md) or [suggesting improvements](https://github.com/PHPOffice/PHPWord/issues/new?labels=Change+Request&template=feature_request.md), or in a more active form like [requesting a pull](https://github.com/PHPOffice/PHPWord/pulls). -We want to create a high quality document writer and reader library that people can use with more confidence and less bugs. We want to collaborate happily, code joyfully, and get alive merrily. Thus, below are some guidelines, that we expect to be followed by each contributor. +We want to create a high quality document writer and reader library that people can use with more confidence and fewer bugs. We want to collaborate happily, code joyfully, and live merrily. Thus, below are some guidelines that we expect to be followed by each contributor: -- **Be brief, but be bold**. State your issues briefly. But speak out your ideas loudly, even if you can't or don't know how to implement it right away. The world will be better with limitless innovations. -- **Follow PHP-FIG standards**. We follow PHP Standards Recommendations (PSRs) by [PHP Framework Interoperability Group](http://www.php-fig.org/). If you're not familiar with these standards, please, [familiarize yourself now](https://github.com/php-fig/fig-standards). Also, please, use [PHPCodeSniffer](http://pear.php.net/package/PHP_CodeSniffer/) to validate your code against PSRs. -- **Test your code**. Nobody else knows your code better than you. So, it's completely yours mission to test the changes you made before pull request submission. We use [PHPUnit](https://phpunit.de/) for our testing purposes and recommend you using this tool too. [Here](https://phpunit.de/presentations.html) you can find PHPUnit best practices and additional information on effective unit testing, which helps us making PHPWord better day to day. Do not hesitate to smoke it carefully. It's a great investment in quality of your work, and it saves you years of life. -- **Request pull in separate branch**. Do not submit your request to the master branch. But create a separate branch named specifically for the issue that you addressed. Read [GitHub manual](https://help.github.com/articles/using-pull-requests) to find out more about this. If you are new to GitHub, read [this short manual](https://help.github.com/articles/fork-a-repo) to get yourself familiar with forks and how git works in general. [This video](http://www.youtube.com/watch?v=-zvHQXnBO6c) explains how to synchronize your Github Fork with the Branch of PHPWord. +- **Be brief, but be bold**. State your issues briefly. But speak out your ideas loudly, even if you can't or don't know how to implement them right away. The world will be better with limitless innovations. +- **Follow PHP-FIG standards**. We follow PHP Standards Recommendations (PSRs) by [PHP Framework Interoperability Group](http://www.php-fig.org/). If you're not familiar with these standards, [familiarize yourself now](https://github.com/php-fig/fig-standards). Also, please run `composer fix` to automatically fix your code to match these recommendations. +- **Test your code**. No one knows your code better than you, so we depend on you to test the changes you make before pull request submission. We use [PHPUnit](https://phpunit.de/) for our testing purposes and request that you use this tool too. Tests can be ran with `composer test`. [Documentation for writing tests with PHPUnit is available on Read the Docs.](https://phpunit.readthedocs.io) +- **Use best practices when submitting pull requests**. Create a separate branch named specifically for the issue that you are addressing. Read the [GitHub manual](https://help.github.com/articles/about-pull-requests) to learn more about pull requests and GitHub. If you are new to GitHub, read [this short manual](https://help.github.com/articles/fork-a-repo) to get yourself familiar with forks and how git works in general. [This video](http://www.youtube.com/watch?v=-zvHQXnBO6c) explains how to synchronize your fork on GitHub with the upstream branch from PHPWord. + +## Getting Started + +1. [Clone](https://help.github.com/en/articles/cloning-a-repository) [PHPWord](https://github.com/PHPOffice/PHPWord/) +2. [Install Composer](https://getcomposer.org/download/) if you don't already have it +3. Open your terminal and: + 1. Switch to the directory PHPWord was cloned to (e.g., `cd ~/Projects/PHPWord/`) + 2. Run `composer install` to install the dependencies + +You're ready to start working on PHPWord! Tests belong in the `/tests/PhpWord/` directory, the source code is in `/src/PhpWord/`, and any documentation should go in `/docs/`. Familiarize yourself with the codebase and try your hand at fixing [one of our outstanding issues](https://github.com/PHPOffice/PHPWord/issues). Before you get started, check the [existing pull requests](https://github.com/PHPOffice/PHPWord/pulls) to make sure no one else is already working on it. + +Once you have an issue you want to start working on, you'll need to write tests for it, and then you can start implementing the changes necessary to pass the new tests. To run the tests, you can run one of the following commands in your terminal: + +- `composer test-no-coverage` to run all of the tests +- `composer test` to run all of the tests and generate test coverage reports + +When you're ready to submit your new (and fully tested) feature, ensure `composer check` passes and [submit a pull request to PHPWord](https://github.com/PHPOffice/PHPWord/issues/new). That's it. Thank you for your interest in PHPWord, and welcome! diff --git a/LICENSE b/LICENSE index 3f6a8c3865..aebd12b0ed 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ PHPWord, a pure PHP library for reading and writing word processing documents. -Copyright (c) 2010-2014 PHPWord. +Copyright (c) 2010-2025 PHPWord. PHPWord is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3 as published by diff --git a/README.md b/README.md index 8f499a8ebb..e080d32da8 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ # ![PHPWord](https://rawgit.com/PHPOffice/PHPWord/develop/docs/images/phpword.svg "PHPWord") -[![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?s=b5997ce59ac2816b4514f3a38de9900f6d492c1d)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/) -[![Code Coverage](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/badges/coverage.png?s=742a98745725c562955440edc8d2c39d7ff5ae25)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/) -[![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) +[![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)](https://packagist.org/packages/phpoffice/phpword) +[![License](https://poser.pugx.org/phpoffice/phpword/license)](https://packagist.org/packages/phpoffice/phpword) + +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](https://github.com/PHPOffice/PHPWord/blob/develop/COPYING.LESSER). PHPWord is aimed to be a high quality software product by incorporating [continuous integration](https://travis-ci.org/PHPOffice/PHPWord) and [unit testing](http://phpoffice.github.io/PHPWord/coverage/develop/). You can learn more about PHPWord by reading the [Developers' Documentation](http://phpword.readthedocs.org/) and the [API Documentation](http://phpoffice.github.io/PHPWord/docs/develop/). +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) Read more about PHPWord: @@ -18,12 +20,11 @@ Read more about PHPWord: - [Installation](#installation) - [Getting started](#getting-started) - [Contributing](#contributing) -- [Developers' Documentation](http://phpword.readthedocs.org/) -- [API Documentation](http://phpoffice.github.io/PHPWord/docs/master/) +- [Developers' Documentation](https://phpoffice.github.io/PHPWord/) ## Features -With PHPWord, you can create DOCX, ODT, or RTF documents dynamically using your PHP 5.3+ scripts. Below are some of the things that you can do with PHPWord library: +With PHPWord, you can create OOXML, ODF, or RTF documents dynamically using your PHP scripts. Below are some of the things that you can do with PHPWord library: - Set document properties, e.g. title, subject, and creator. - Create document sections with different settings, e.g. portrait/landscape, page size, and page numbering @@ -44,40 +45,34 @@ With PHPWord, you can create DOCX, ODT, or RTF documents dynamically using your - 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 main document part of OOXML template +- Use XSL 1.0 style sheets to transform headers, main document part, and footers of an OOXML template - ... and many more features on progress ## Requirements PHPWord requires the following: -- PHP 5.3+ -- [Zip extension](http://php.net/manual/en/book.zip.php) +- PHP 7.1+ - [XML Parser extension](http://www.php.net/manual/en/xml.installation.php) +- [Laminas Escaper component](https://docs.laminas.dev/laminas-escaper/intro/) +- [Zip extension](http://php.net/manual/en/book.zip.php) (optional, used to write OOXML and ODF) - [GD extension](http://php.net/manual/en/book.image.php) (optional, used to add images) -- [XMLWriter extension](http://php.net/manual/en/book.xmlwriter.php) (optional, used to write DOCX and ODT) +- [XMLWriter extension](http://php.net/manual/en/book.xmlwriter.php) (optional, used to write OOXML and ODF) - [XSL extension](http://php.net/manual/en/book.xsl.php) (optional, used to apply XSL style sheet to template ) -- [dompdf](https://github.com/dompdf/dompdf) (optional, used to write PDF) +- [dompdf library](https://github.com/dompdf/dompdf) (optional, used to write PDF) ## Installation -It is recommended that you install the PHPWord library [through composer](http://getcomposer.org/). To do so, add -the following lines to your ``composer.json``. +PHPWord is installed via [Composer](https://getcomposer.org/). +To [add a dependency](https://getcomposer.org/doc/04-schema.md#package-links) to PHPWord in your project, either -```json -{ - "require": { - "phpoffice/phpword": "dev-master" - } -} +Run the following to use the latest stable version +```sh +composer require phpoffice/phpword ``` - -Alternatively, you can download the latest release from the [releases page](https://github.com/PHPOffice/PHPWord/releases). -In this case, you will have to register the autoloader. - -```php -require_once 'path/to/PhpWord/src/PhpWord/Autoloader.php'; -\PhpOffice\PhpWord\Autoloader::register(); +or if you want the latest unreleased version +```sh +composer require phpoffice/phpword:dev-master ``` ## Getting started @@ -86,8 +81,6 @@ The following is a basic usage example of the PHPWord library. ```php addSection(); // Adding Text element to the Section having font styled by default... $section->addText( - htmlspecialchars( - '"Learn from yesterday, live for today, hope for tomorrow. ' - . 'The important thing is not to stop questioning." ' - . '(Albert Einstein)' - ) + '"Learn from yesterday, live for today, hope for tomorrow. ' + . 'The important thing is not to stop questioning." ' + . '(Albert Einstein)' ); /* @@ -114,11 +105,9 @@ $section->addText( // Adding Text element with font customized inline... $section->addText( - htmlspecialchars( - '"Great achievement is usually born of great sacrifice, ' - . 'and is never the result of selfishness." ' - . '(Napoleon Hill)' - ), + '"Great achievement is usually born of great sacrifice, ' + . 'and is never the result of selfishness." ' + . '(Napoleon Hill)', array('name' => 'Tahoma', 'size' => 10) ); @@ -129,11 +118,9 @@ $phpWord->addFontStyle( array('name' => 'Tahoma', 'size' => 10, 'color' => '1B2232', 'bold' => true) ); $section->addText( - htmlspecialchars( - '"The greatest accomplishment is not in never falling, ' - . 'but in rising again after you fall." ' - . '(Vince Lombardi)' - ), + '"The greatest accomplishment is not in never falling, ' + . 'but in rising again after you fall." ' + . '(Vince Lombardi)', $fontStyleName ); @@ -142,9 +129,7 @@ $fontStyle = new \PhpOffice\PhpWord\Style\Font(); $fontStyle->setBold(true); $fontStyle->setName('Tahoma'); $fontStyle->setSize(13); -$myTextElement = $section->addText( - htmlspecialchars('"Believe you can and you\'re halfway there." (Theodor Roosevelt)') -); +$myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodor Roosevelt)'); $myTextElement->setFontStyle($fontStyle); // Saving the document as OOXML file... @@ -162,15 +147,15 @@ $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. */ ``` -:warning: Escape any string you pass to OOXML/ODF/HTML document, otherwise it may get broken. -More examples are provided in the [samples folder](samples/). You can also read the [Developers' Documentation](http://phpword.readthedocs.org/) and the [API Documentation](http://phpoffice.github.io/PHPWord/docs/master/) for more detail. +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](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](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 [develop](https://github.com/PHPOffice/PHPWord/tree/develop) branch. +- 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 [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/VERSION b/VERSION deleted file mode 100644 index d33c3a2128..0000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.12.0 \ No newline at end of file diff --git a/composer.json b/composer.json index b55d8340ca..08ef4ec927 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,14 @@ { "name": "phpoffice/phpword", - "description": "PHPWord - A pure PHP library for reading and writing word processing documents (DOCX, ODT, RTF, HTML, PDF)", + "description": "PHPWord - A pure PHP library for reading and writing word processing documents (OOXML, ODF, RTF, HTML, PDF)", "keywords": [ - "PHP", "PhpOffice", "office", "PhpWord", "word", "template", "template processor", "reader", "writer", + "PHP", "PHPOffice", "office", "PHPWord", "word", "template", "template processor", "reader", "writer", "docx", "OOXML", "OpenXML", "Office Open XML", "ISO IEC 29500", "WordprocessingML", - "RTF", "Rich Text Format", "doc", "odt", "OpenDocument", "PDF", "HTML" + "RTF", "Rich Text Format", "doc", "odt", "ODF", "OpenDocument", "PDF", "HTML" ], - "homepage": "http://phpoffice.github.io/", + "homepage": "https://phpoffice.github.io/PHPWord/", "type": "library", - "license": "LGPL-3.0", + "license": "LGPL-3.0-only", "authors": [ { "name": "Mark Baker" @@ -20,7 +20,7 @@ }, { "name": "Franck Lefevre", - "homepage": "http://blog.rootslabs.net/" + "homepage": "https://rootslabs.net/blog/" }, { "name": "Ivan Lanin", @@ -29,33 +29,116 @@ { "name": "Roman Syroeshko", "homepage": "http://ru.linkedin.com/pub/roman-syroeshko/34/a53/994/" + }, + { + "name": "Antoine de Troostembergh" } ], + "scripts": { + "test": [ + "@php vendor/bin/phpunit --color=always" + ], + "test-no-coverage": [ + "@php vendor/bin/phpunit --color=always --no-coverage" + ], + "check": [ + "@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 vendor/bin/php-cs-fixer fix --ansi" + ], + "samples": [ + "php samples/Sample_01_SimpleText.php", + "php samples/Sample_02_TabStops.php", + "php samples/Sample_03_Sections.php", + "php samples/Sample_04_Textrun.php", + "php samples/Sample_05_Multicolumn.php", + "php samples/Sample_06_Footnote.php", + "php samples/Sample_07_TemplateCloneRow.php", + "php samples/Sample_08_ParagraphPagination.php", + "php samples/Sample_09_Tables.php", + "php samples/Sample_10_EastAsianFontStyle.php", + "php samples/Sample_11_ReadWord97.php", + "php samples/Sample_11_ReadWord2007.php", + "php samples/Sample_12_HeaderFooter.php", + "php samples/Sample_13_Images.php", + "php samples/Sample_14_ListItem.php", + "php samples/Sample_15_Link.php", + "php samples/Sample_16_Object.php", + "php samples/Sample_17_TitleTOC.php", + "php samples/Sample_18_Watermark.php", + "php samples/Sample_19_TextBreak.php", + "php samples/Sample_20_BGColor.php", + "php samples/Sample_21_TableRowRules.php", + "php samples/Sample_22_CheckBox.php", + "php samples/Sample_23_TemplateBlock.php", + "php samples/Sample_24_ReadODText.php", + "php samples/Sample_25_TextBox.php", + "php samples/Sample_26_Html.php", + "php samples/Sample_27_Field.php", + "php samples/Sample_28_ReadRTF.php", + "php samples/Sample_29_Line.php", + "php samples/Sample_30_ReadHTML.php", + "php samples/Sample_31_Shape.php", + "php samples/Sample_32_Chart.php", + "php samples/Sample_33_FormField.php", + "php samples/Sample_34_SDT.php", + "php samples/Sample_35_InternalLink.php", + "php samples/Sample_36_RTL.php", + "php samples/Sample_37_Comments.php", + "php samples/Sample_38_Protection.php", + "php samples/Sample_39_TrackChanges.php", + "php samples/Sample_40_TemplateSetComplexValue.php", + "php samples/Sample_41_TemplateSetChart.php", + "php samples/Sample_42_TemplateSetCheckbox.php", + "php samples/Sample_43_RTLDefault.php", + "php samples/Sample_44_ExtractVariablesFromReaderWord2007.php", + "php samples/Sample_45_Autoloader.php" + ] + }, + "scripts-descriptions": { + "test": "Runs all unit tests", + "test-no-coverage": "Runs all unit tests, without code coverage", + "check": "Runs PHP CheckStyle and PHP Mess detector", + "fix": "Fixes issues found by PHP-CS" + }, "require": { - "php": ">=5.3.3", - "ext-xml": "*" + "php": "^7.1|^8.0", + "ext-dom": "*", + "ext-gd": "*", + "ext-zip": "*", + "ext-json": "*", + "ext-xml": "*", + "phpoffice/math": "^0.3" }, "require-dev": { - "phpunit/phpunit": "3.7.*", - "phpdocumentor/phpdocumentor":"2.*", - "squizlabs/php_codesniffer": "1.*", - "phpmd/phpmd": "2.*", - "sebastian/phpcpd": "2.*", - "phploc/phploc": "2.*", - "dompdf/dompdf":"0.6.*", - "tecnick.com/tcpdf": "6.*", - "mpdf/mpdf": "5.*" + "ext-libxml": "*", + "dompdf/dompdf": "^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.3", + "mpdf/mpdf": "^7.0 || ^8.0", + "phpmd/phpmd": "^2.13", + "phpstan/phpstan": "^0.12.88 || ^1.0.0 || ^2.0.0", + "phpstan/phpstan-phpunit": "^1.0 || ^2.0", + "phpunit/phpunit": ">=7.0", + "symfony/process": "^4.4 || ^5.0", + "tecnickcom/tcpdf": "^6.5" }, "suggest": { - "ext-zip": "Used to write DOCX and ODT", - "ext-gd2": "Used to add images", - "ext-xmlwriter": "Used to write DOCX and ODT", - "ext-xsl": "Used to apply XSL style sheet to main document part of OOXML template", - "dompdf/dompdf": "Used to write PDF" + "ext-xmlwriter": "Allows writing OOXML and ODF", + "ext-xsl": "Allows applying XSL style sheet to headers, to main document part, and to footers of an OOXML template", + "dompdf/dompdf": "Allows writing PDF" }, "autoload": { "psr-4": { "PhpOffice\\PhpWord\\": "src/PhpWord" } + }, + "autoload-dev": { + "psr-4": { + "PhpOffice\\PhpWordTests\\": "tests/PhpWordTests" + } } } diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 47351b0efd..0000000000 --- a/composer.lock +++ /dev/null @@ -1,3879 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "hash": "1a31c30080e1b0b550cdb47b7f764ca6", - "packages": [], - "packages-dev": [ - { - "name": "cilex/cilex", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/Cilex/Cilex.git", - "reference": "7acd965a609a56d0345e8b6071c261fbdb926cb5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Cilex/Cilex/zipball/7acd965a609a56d0345e8b6071c261fbdb926cb5", - "reference": "7acd965a609a56d0345e8b6071c261fbdb926cb5", - "shasum": "" - }, - "require": { - "cilex/console-service-provider": "1.*", - "php": ">=5.3.3", - "pimple/pimple": "~1.0", - "symfony/finder": "~2.1", - "symfony/process": "~2.1" - }, - "require-dev": { - "phpunit/phpunit": "3.7.*", - "symfony/validator": "~2.1" - }, - "suggest": { - "monolog/monolog": ">=1.0.0", - "symfony/validator": ">=1.0.0", - "symfony/yaml": ">=1.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "Cilex": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "description": "The PHP micro-framework for Command line tools based on the Symfony2 Components", - "homepage": "http://cilex.github.com/", - "keywords": [ - "cli", - "microframework" - ], - "time": "2014-03-29 14:03:13" - }, - { - "name": "cilex/console-service-provider", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/Cilex/console-service-provider.git", - "reference": "25ee3d1875243d38e1a3448ff94bdf944f70d24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Cilex/console-service-provider/zipball/25ee3d1875243d38e1a3448ff94bdf944f70d24e", - "reference": "25ee3d1875243d38e1a3448ff94bdf944f70d24e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "pimple/pimple": "1.*@dev", - "symfony/console": "~2.1" - }, - "require-dev": { - "cilex/cilex": "1.*@dev", - "silex/silex": "1.*@dev" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "Cilex\\Provider\\Console": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com/" - }, - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "description": "Console Service Provider", - "keywords": [ - "cilex", - "console", - "pimple", - "service-provider", - "silex" - ], - "time": "2012-12-19 10:50:58" - }, - { - "name": "doctrine/annotations", - "version": "v1.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "6a6bec0670bb6e71a263b08bc1b98ea242928633" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/6a6bec0670bb6e71a263b08bc1b98ea242928633", - "reference": "6a6bec0670bb6e71a263b08bc1b98ea242928633", - "shasum": "" - }, - "require": { - "doctrine/lexer": "1.*", - "php": ">=5.3.2" - }, - "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "4.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\Common\\Annotations\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org/", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "time": "2014-09-25 16:45:30" - }, - { - "name": "doctrine/lexer", - "version": "v1.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/2f708a85bb3aab5d99dab8be435abd73e0b18acb", - "reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "type": "library", - "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com", - "homepage": "http://www.instaclick.com/" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "http://jmsyst.com/", - "role": "Developer of wrapped JMSSerializerBundle" - } - ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org/", - "keywords": [ - "lexer", - "parser" - ], - "time": "2013-01-12 18:59:04" - }, - { - "name": "dompdf/dompdf", - "version": "v0.6.1", - "source": { - "type": "git", - "url": "https://github.com/dompdf/dompdf.git", - "reference": "cf7d8a0a27270418850cc7d7ea532159e5eeb3eb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/cf7d8a0a27270418850cc7d7ea532159e5eeb3eb", - "reference": "cf7d8a0a27270418850cc7d7ea532159e5eeb3eb", - "shasum": "" - }, - "require": { - "phenx/php-font-lib": "0.2.*" - }, - "type": "library", - "autoload": { - "classmap": [ - "include/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL" - ], - "authors": [ - { - "name": "Fabien Ménager", - "email": "fabien.menager@gmail.com" - }, - { - "name": "Brian Sweeney", - "email": "eclecticgeek@gmail.com" - } - ], - "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", - "homepage": "https://github.com/dompdf/dompdf", - "time": "2014-03-11 01:59:52" - }, - { - "name": "erusev/parsedown", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "495e7ac73bb5fde6b857b88ff2bb1b5e79a4263a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/495e7ac73bb5fde6b857b88ff2bb1b5e79a4263a", - "reference": "495e7ac73bb5fde6b857b88ff2bb1b5e79a4263a", - "shasum": "" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com/" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org/", - "keywords": [ - "markdown", - "parser" - ], - "time": "2014-11-29 02:29:14" - }, - { - "name": "herrera-io/json", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/herrera-io/php-json.git", - "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/herrera-io/php-json/zipball/60c696c9370a1e5136816ca557c17f82a6fa83f1", - "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1", - "shasum": "" - }, - "require": { - "ext-json": "*", - "justinrainbow/json-schema": ">=1.0,<2.0-dev", - "php": ">=5.3.3", - "seld/jsonlint": ">=1.0,<2.0-dev" - }, - "require-dev": { - "herrera-io/phpunit-test-case": "1.*", - "mikey179/vfsstream": "1.1.0", - "phpunit/phpunit": "3.7.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "files": [ - "src/lib/json_version.php" - ], - "psr-0": { - "Herrera\\Json": "src/lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kevin Herrera", - "email": "kevin@herrera.io", - "homepage": "http://kevin.herrera.io/", - "role": "Developer" - } - ], - "description": "A library for simplifying JSON linting and validation.", - "homepage": "http://herrera-io.github.com/php-json", - "keywords": [ - "json", - "lint", - "schema", - "validate" - ], - "time": "2013-10-30 16:51:34" - }, - { - "name": "herrera-io/phar-update", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/herrera-io/php-phar-update.git", - "reference": "00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/herrera-io/php-phar-update/zipball/00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b", - "reference": "00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b", - "shasum": "" - }, - "require": { - "herrera-io/json": "1.*", - "kherge/version": "1.*", - "php": ">=5.3.3" - }, - "require-dev": { - "herrera-io/phpunit-test-case": "1.*", - "mikey179/vfsstream": "1.1.0", - "phpunit/phpunit": "3.7.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "files": [ - "src/lib/constants.php" - ], - "psr-0": { - "Herrera\\Phar\\Update": "src/lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kevin Herrera", - "email": "kevin@herrera.io", - "homepage": "http://kevin.herrera.io/", - "role": "Developer" - } - ], - "description": "A library for self-updating Phars.", - "homepage": "http://herrera-io.github.com/php-phar-update", - "keywords": [ - "phar", - "update" - ], - "time": "2013-10-30 17:23:01" - }, - { - "name": "jms/metadata", - "version": "1.5.1", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/metadata.git", - "reference": "22b72455559a25777cfd28c4ffda81ff7639f353" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/22b72455559a25777cfd28c4ffda81ff7639f353", - "reference": "22b72455559a25777cfd28c4ffda81ff7639f353", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "doctrine/cache": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5.x-dev" - } - }, - "autoload": { - "psr-0": { - "Metadata\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache" - ], - "authors": [ - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", - "role": "Developer of wrapped JMSSerializerBundle" - } - ], - "description": "Class/method/property metadata management in PHP", - "keywords": [ - "annotations", - "metadata", - "xml", - "yaml" - ], - "time": "2014-07-12 07:13:19" - }, - { - "name": "jms/parser-lib", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/parser-lib.git", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/parser-lib/zipball/c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "shasum": "" - }, - "require": { - "phpoption/phpoption": ">=0.9,<2.0-dev" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "JMS\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "description": "A library for easily creating recursive-descent parsers.", - "time": "2012-11-18 18:08:43" - }, - { - "name": "jms/serializer", - "version": "0.16.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/serializer.git", - "reference": "c8a171357ca92b6706e395c757f334902d430ea9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/c8a171357ca92b6706e395c757f334902d430ea9", - "reference": "c8a171357ca92b6706e395c757f334902d430ea9", - "shasum": "" - }, - "require": { - "doctrine/annotations": "1.*", - "jms/metadata": "~1.1", - "jms/parser-lib": "1.*", - "php": ">=5.3.2", - "phpcollection/phpcollection": "~0.1" - }, - "require-dev": { - "doctrine/orm": "~2.1", - "doctrine/phpcr-odm": "~1.0.1", - "jackalope/jackalope-doctrine-dbal": "1.0.*", - "propel/propel1": "~1.7", - "symfony/filesystem": "2.*", - "symfony/form": "~2.1", - "symfony/translation": "~2.0", - "symfony/validator": "~2.0", - "symfony/yaml": "2.*", - "twig/twig": ">=1.8,<2.0-dev" - }, - "suggest": { - "symfony/yaml": "Required if you'd like to serialize data to YAML format." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.15-dev" - } - }, - "autoload": { - "psr-0": { - "JMS\\Serializer": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "http://jmsyst.com/", - "role": "Developer of wrapped JMSSerializerBundle" - } - ], - "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", - "homepage": "http://jmsyst.com/libs/serializer", - "keywords": [ - "deserialization", - "jaxb", - "json", - "serialization", - "xml" - ], - "time": "2014-03-18 08:39:00" - }, - { - "name": "justinrainbow/json-schema", - "version": "1.3.7", - "source": { - "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "87b54b460febed69726c781ab67462084e97a105" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/87b54b460febed69726c781ab67462084e97a105", - "reference": "87b54b460febed69726c781ab67462084e97a105", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "json-schema/json-schema-test-suite": "1.1.0", - "phpdocumentor/phpdocumentor": "~2", - "phpunit/phpunit": "~3.7" - }, - "bin": [ - "bin/validate-json" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "psr-0": { - "JsonSchema": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" - } - ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], - "time": "2014-08-25 02:48:14" - }, - { - "name": "kherge/version", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/kherge-unmaintained/Version.git", - "reference": "f07cf83f8ce533be8f93d2893d96d674bbeb7e30" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/kherge-unmaintained/Version/zipball/f07cf83f8ce533be8f93d2893d96d674bbeb7e30", - "reference": "f07cf83f8ce533be8f93d2893d96d674bbeb7e30", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "KevinGH\\Version": "src/lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kevin Herrera", - "email": "me@kevingh.com", - "homepage": "http://www.kevingh.com/" - } - ], - "description": "A parsing and comparison library for semantic versioning.", - "homepage": "http://github.com/kherge/Version", - "time": "2012-08-16 17:13:03" - }, - { - "name": "monolog/monolog", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "ec3961874c43840e96da3a8a1ed20d8c73d7e5aa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/ec3961874c43840e96da3a8a1ed20d8c73d7e5aa", - "reference": "ec3961874c43840e96da3a8a1ed20d8c73d7e5aa", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" - }, - "provide": { - "psr/log-implementation": "1.0.0" - }, - "require-dev": { - "aws/aws-sdk-php": "~2.4, >2.4.8", - "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "phpunit/phpunit": "~3.7.0", - "raven/raven": "~0.5", - "ruflin/elastica": "0.90.*", - "videlalvaro/php-amqplib": "~2.4" - }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "raven/raven": "Allow sending log messages to a Sentry server", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "videlalvaro/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.11.x-dev" - } - }, - "autoload": { - "psr-4": { - "Monolog\\": "src/Monolog" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be/" - } - ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", - "keywords": [ - "log", - "logging", - "psr-3" - ], - "time": "2014-09-30 13:30:58" - }, - { - "name": "mpdf/mpdf", - "version": "v5.7.3", - "source": { - "type": "git", - "url": "https://github.com/finwe/mpdf.git", - "reference": "ace190986978df40b9c416cf7ba8761945fc1758" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/finwe/mpdf/zipball/ace190986978df40b9c416cf7ba8761945fc1758", - "reference": "ace190986978df40b9c416cf7ba8761945fc1758", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=4.3.10" - }, - "type": "library", - "autoload": { - "classmap": [ - "mpdf.php", - "classes" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-1.0+" - ], - "authors": [ - { - "name": "Ian Back" - } - ], - "description": "A PHP class to generate PDF files from HTML with Unicode/UTF-8 and CJK support", - "homepage": "http://www.mpdf1.com/mpdf/index.php", - "keywords": [ - "pdf", - "php", - "utf-8" - ], - "time": "2014-08-24 08:33:20" - }, - { - "name": "nikic/php-parser", - "version": "v0.9.5", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ef70767475434bdb3615b43c327e2cae17ef12eb", - "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.9-dev" - } - }, - "autoload": { - "psr-0": { - "PHPParser": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "time": "2014-07-23 18:24:17" - }, - { - "name": "pdepend/pdepend", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/pdepend/pdepend.git", - "reference": "1b0acf162da4f30237987e61e177a57f78e3d87e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pdepend/pdepend/zipball/1b0acf162da4f30237987e61e177a57f78e3d87e", - "reference": "1b0acf162da4f30237987e61e177a57f78e3d87e", - "shasum": "" - }, - "require": { - "symfony/config": ">=2.4", - "symfony/dependency-injection": ">=2.4", - "symfony/filesystem": ">=2.4" - }, - "require-dev": { - "phpunit/phpunit": "4.*@stable", - "squizlabs/php_codesniffer": "@stable" - }, - "bin": [ - "src/bin/pdepend" - ], - "type": "library", - "autoload": { - "psr-0": { - "PDepend\\": "src/main/php/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Official version of pdepend to be handled with Composer", - "time": "2014-12-04 12:38:39" - }, - { - "name": "phenx/php-font-lib", - "version": "0.2.2", - "source": { - "type": "git", - "url": "https://github.com/PhenX/php-font-lib.git", - "reference": "c30c7fc00a6b0d863e9bb4c5d5dd015298b2dc82" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/c30c7fc00a6b0d863e9bb4c5d5dd015298b2dc82", - "reference": "c30c7fc00a6b0d863e9bb4c5d5dd015298b2dc82", - "shasum": "" - }, - "type": "library", - "autoload": { - "classmap": [ - "classes/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL" - ], - "authors": [ - { - "name": "Fabien Ménager", - "email": "fabien.menager@gmail.com" - } - ], - "description": "A library to read, parse, export and make subsets of different types of font files.", - "homepage": "https://github.com/PhenX/php-font-lib", - "time": "2014-02-01 15:22:28" - }, - { - "name": "phpcollection/phpcollection", - "version": "0.4.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/php-collection.git", - "reference": "b8bf55a0a929ca43b01232b36719f176f86c7e83" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-collection/zipball/b8bf55a0a929ca43b01232b36719f176f86c7e83", - "reference": "b8bf55a0a929ca43b01232b36719f176f86c7e83", - "shasum": "" - }, - "require": { - "phpoption/phpoption": "1.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.3-dev" - } - }, - "autoload": { - "psr-0": { - "PhpCollection": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "http://jmsyst.com/", - "role": "Developer of wrapped JMSSerializerBundle" - } - ], - "description": "General-Purpose Collection Library for PHP", - "keywords": [ - "collection", - "list", - "map", - "sequence", - "set" - ], - "time": "2014-03-11 13:46:42" - }, - { - "name": "phpdocumentor/fileset", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/Fileset.git", - "reference": "bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/Fileset/zipball/bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0", - "reference": "bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "symfony/finder": "~2.1" - }, - "require-dev": { - "phpunit/phpunit": "~3.7" - }, - "type": "library", - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/", - "tests/unit/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Fileset component for collecting a set of files given directories and file paths", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "files", - "fileset", - "phpdoc" - ], - "time": "2013-08-06 21:07:42" - }, - { - "name": "phpdocumentor/graphviz", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/GraphViz.git", - "reference": "aa243118c8a055fc853c02802e8503c5435862f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/GraphViz/zipball/aa243118c8a055fc853c02802e8503c5435862f7", - "reference": "aa243118c8a055fc853c02802e8503c5435862f7", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~3.7" - }, - "type": "library", - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/", - "tests/unit" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "time": "2014-07-19 06:52:59" - }, - { - "name": "phpdocumentor/phpdocumentor", - "version": "v2.8.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/phpDocumentor2.git", - "reference": "5920dd42a5a92e4486f342ba8ded979db149ceb2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/phpDocumentor2/zipball/5920dd42a5a92e4486f342ba8ded979db149ceb2", - "reference": "5920dd42a5a92e4486f342ba8ded979db149ceb2", - "shasum": "" - }, - "require": { - "cilex/cilex": "~1.0", - "dompdf/dompdf": "~0.6", - "erusev/parsedown": "~1.0", - "herrera-io/phar-update": "1.0.3", - "jms/serializer": "~0.12", - "monolog/monolog": "~1.6", - "php": ">=5.3.3", - "phpdocumentor/fileset": "~1.0", - "phpdocumentor/graphviz": "~1.0", - "phpdocumentor/reflection": "~1.0", - "phpdocumentor/reflection-docblock": "~2.0", - "phpdocumentor/template-abstract": "~1.2", - "phpdocumentor/template-checkstyle": "~1.2", - "phpdocumentor/template-clean": "~1.0", - "phpdocumentor/template-new-black": "~1.3", - "phpdocumentor/template-old-ocean": "~1.3", - "phpdocumentor/template-responsive": "~1.3", - "phpdocumentor/template-responsive-twig": "~1.2", - "phpdocumentor/template-xml": "~1.0", - "phpdocumentor/template-zend": "~1.3", - "symfony/config": "~2.3", - "symfony/console": "~2.3", - "symfony/event-dispatcher": "~2.1", - "symfony/process": "~2.0", - "symfony/stopwatch": "~2.3", - "symfony/validator": "~2.2", - "twig/twig": "~1.3", - "zendframework/zend-cache": "~2.1", - "zendframework/zend-config": "~2.1", - "zendframework/zend-filter": "~2.1", - "zendframework/zend-i18n": "~2.1", - "zendframework/zend-serializer": "~2.1", - "zendframework/zend-servicemanager": "~2.1", - "zendframework/zend-stdlib": "~2.1", - "zetacomponents/document": ">=1.3.1" - }, - "require-dev": { - "behat/behat": "~2.4", - "mikey179/vfsstream": "~1.2", - "mockery/mockery": "~0.9@dev", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.4", - "symfony/expression-language": "~2.4" - }, - "suggest": { - "ext-twig": "Enabling the twig extension improves the generation of twig based templates.", - "ext-xslcache": "Enabling the XSLCache extension improves the generation of xml based templates." - }, - "bin": [ - "bin/phpdoc.php", - "bin/phpdoc" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-develop": "2.9-dev" - } - }, - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/", - "tests/unit/" - ], - "Cilex\\Provider": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Documentation Generator for PHP", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "api", - "application", - "dga", - "documentation", - "phpdoc" - ], - "time": "2014-11-11 14:08:43" - }, - { - "name": "phpdocumentor/reflection", - "version": "1.0.7", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/Reflection.git", - "reference": "fc40c3f604ac2287eb5c314174d5109b2c699372" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/fc40c3f604ac2287eb5c314174d5109b2c699372", - "reference": "fc40c3f604ac2287eb5c314174d5109b2c699372", - "shasum": "" - }, - "require": { - "nikic/php-parser": "~0.9.4", - "php": ">=5.3.3", - "phpdocumentor/reflection-docblock": "~2.0", - "psr/log": "~1.0" - }, - "require-dev": { - "behat/behat": "~2.4", - "mockery/mockery": "~0.8", - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/", - "tests/unit/", - "tests/mocks/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Reflection library to do Static Analysis for PHP Projects", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "time": "2014-11-14 11:43:04" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "38743b677965c48a637097b2746a281264ae2347" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/38743b677965c48a637097b2746a281264ae2347", - "reference": "38743b677965c48a637097b2746a281264ae2347", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "3.7.*@stable" - }, - "suggest": { - "dflydev/markdown": "1.0.*", - "erusev/parsedown": "~0.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "time": "2014-08-09 10:27:07" - }, - { - "name": "phpdocumentor/template-abstract", - "version": "1.2.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/template.abstract.git", - "reference": "df1d11cf11cf5da433789e2be07f4d2d6e51aaca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/template.abstract/zipball/df1d11cf11cf5da433789e2be07f4d2d6e51aaca", - "reference": "df1d11cf11cf5da433789e2be07f4d2d6e51aaca", - "shasum": "" - }, - "require": { - "phpdocumentor/unified-asset-installer": "~1.1" - }, - "type": "phpdocumentor-template", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Simple bright template for phpDocumentor", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "documentation", - "phpdoc", - "template" - ], - "time": "2014-06-04 19:32:56" - }, - { - "name": "phpdocumentor/template-checkstyle", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/template.checkstyle.git", - "reference": "cfa86d19327b0d762332787ff2dda0d55226a2e2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/template.checkstyle/zipball/cfa86d19327b0d762332787ff2dda0d55226a2e2", - "reference": "cfa86d19327b0d762332787ff2dda0d55226a2e2", - "shasum": "" - }, - "require": { - "phpdocumentor/unified-asset-installer": "~1.1" - }, - "type": "phpdocumentor-template", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Checkstyle XML output template for phpDocumentor2", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "documentation", - "phpdoc", - "template" - ], - "time": "2014-08-17 19:32:38" - }, - { - "name": "phpdocumentor/template-clean", - "version": "1.0.6", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/template.clean.git", - "reference": "6fc0f7f6c55c1f94ac5b1c6fccde7aac77755e45" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/template.clean/zipball/6fc0f7f6c55c1f94ac5b1c6fccde7aac77755e45", - "reference": "6fc0f7f6c55c1f94ac5b1c6fccde7aac77755e45", - "shasum": "" - }, - "require": { - "phpdocumentor/unified-asset-installer": "~1.1" - }, - "type": "phpdocumentor-template", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A clean, responsive modern template for phpDocumentor for Twig aimed at usability", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "documentation", - "phpdoc", - "responsive", - "template" - ], - "time": "2014-08-15 21:45:34" - }, - { - "name": "phpdocumentor/template-new-black", - "version": "1.3.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/template.new_black.git", - "reference": "d98f84633b94b279582735aecd91015c1e191d98" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/template.new_black/zipball/d98f84633b94b279582735aecd91015c1e191d98", - "reference": "d98f84633b94b279582735aecd91015c1e191d98", - "shasum": "" - }, - "require": { - "phpdocumentor/template-abstract": "1.*", - "phpdocumentor/unified-asset-installer": "~1.1" - }, - "type": "phpdocumentor-template", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Web 2.0 template with dark sidebar for phpDocumentor", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "documentation", - "phpdoc", - "template" - ], - "time": "2014-06-27 17:00:31" - }, - { - "name": "phpdocumentor/template-old-ocean", - "version": "1.3.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/template.old_ocean.git", - "reference": "2fdb786038351c0ec88633d4e2aa103e4bbb8655" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/template.old_ocean/zipball/2fdb786038351c0ec88633d4e2aa103e4bbb8655", - "reference": "2fdb786038351c0ec88633d4e2aa103e4bbb8655", - "shasum": "" - }, - "require": { - "phpdocumentor/unified-asset-installer": "~1.1" - }, - "type": "phpdocumentor-template", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Blue template with high contrast for the foreground", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "documentation", - "phpdoc", - "template" - ], - "time": "2014-06-27 16:59:35" - }, - { - "name": "phpdocumentor/template-responsive", - "version": "1.3.5", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/template.responsive.git", - "reference": "949e742f350f70fc8ec7c945b3cf0070a4e1825e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/template.responsive/zipball/949e742f350f70fc8ec7c945b3cf0070a4e1825e", - "reference": "949e742f350f70fc8ec7c945b3cf0070a4e1825e", - "shasum": "" - }, - "require": { - "phpdocumentor/unified-asset-installer": "~1.1" - }, - "type": "phpdocumentor-template", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Responsive modern template for phpDocumentor", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "documentation", - "phpdoc", - "template" - ], - "time": "2014-08-05 20:47:53" - }, - { - "name": "phpdocumentor/template-responsive-twig", - "version": "1.2.5", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/template.responsive-twig.git", - "reference": "493e204be607583efd2d75f1728cd5210e23cf96" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/template.responsive-twig/zipball/493e204be607583efd2d75f1728cd5210e23cf96", - "reference": "493e204be607583efd2d75f1728cd5210e23cf96", - "shasum": "" - }, - "require": { - "phpdocumentor/unified-asset-installer": "~1.1" - }, - "type": "phpdocumentor-template", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Responsive modern template for phpDocumentor for Twig", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "documentation", - "phpdoc", - "template" - ], - "time": "2014-07-30 20:00:37" - }, - { - "name": "phpdocumentor/template-xml", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/mvriel/template.xml.git", - "reference": "a372713be8ee99b16497e2580592e474ff51190c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mvriel/template.xml/zipball/a372713be8ee99b16497e2580592e474ff51190c", - "reference": "a372713be8ee99b16497e2580592e474ff51190c", - "shasum": "" - }, - "require": { - "phpdocumentor/unified-asset-installer": "~1.1" - }, - "type": "phpdocumentor-template", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generates an XML representation of the project's structure", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "documentation", - "phpdoc", - "template" - ], - "time": "2013-08-01 20:23:32" - }, - { - "name": "phpdocumentor/template-zend", - "version": "1.3.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/template.zend.git", - "reference": "75913288bfd73d3bf4c1b1179c3963f3431e7a9d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/template.zend/zipball/75913288bfd73d3bf4c1b1179c3963f3431e7a9d", - "reference": "75913288bfd73d3bf4c1b1179c3963f3431e7a9d", - "shasum": "" - }, - "require": { - "ext-xsl": "*", - "phpdocumentor/template-abstract": "1.*", - "phpdocumentor/unified-asset-installer": "~1.1" - }, - "type": "phpdocumentor-template", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Official Zend Framework Template for phpDocumentor2", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "ZendFramework", - "documentation", - "phpdoc", - "template", - "zend", - "zf" - ], - "time": "2013-12-05 08:51:57" - }, - { - "name": "phpdocumentor/unified-asset-installer", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/UnifiedAssetInstaller.git", - "reference": "241fb036268cd9da7d76da3db66e3eda66259c52" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/UnifiedAssetInstaller/zipball/241fb036268cd9da7d76da3db66e3eda66259c52", - "reference": "241fb036268cd9da7d76da3db66e3eda66259c52", - "shasum": "" - }, - "require": { - "composer-plugin-api": "1.0.0" - }, - "require-dev": { - "composer/composer": "~1.0@dev", - "phpunit/phpunit": "~3.7" - }, - "type": "composer-installer", - "extra": { - "class": "\\phpDocumentor\\Composer\\UnifiedAssetInstaller" - }, - "autoload": { - "psr-0": { - "phpDocumentor\\Composer": [ - "src/", - "test/unit/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Asset installer for phpDocumentor", - "homepage": "http://www.phpdoc.org/", - "keywords": [ - "assets", - "installer", - "plugins", - "templates" - ], - "time": "2013-09-09 06:13:02" - }, - { - "name": "phploc/phploc", - "version": "2.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phploc.git", - "reference": "322ad07c112d5c6832abed4269d648cacff5959b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phploc/zipball/322ad07c112d5c6832abed4269d648cacff5959b", - "reference": "322ad07c112d5c6832abed4269d648cacff5959b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/finder-facade": "~1.1", - "sebastian/git": "~1.0", - "sebastian/version": "~1.0", - "symfony/console": "~2.2" - }, - "bin": [ - "phploc" - ], - "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": "A tool for quickly measuring the size of a PHP project.", - "homepage": "https://github.com/sebastianbergmann/phploc", - "time": "2014-06-25 08:11:02" - }, - { - "name": "phpmd/phpmd", - "version": "2.1.3", - "source": { - "type": "git", - "url": "https://github.com/phpmd/phpmd.git", - "reference": "1a485d9db869137af5e9678bd844568c92998b25" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpmd/phpmd/zipball/1a485d9db869137af5e9678bd844568c92998b25", - "reference": "1a485d9db869137af5e9678bd844568c92998b25", - "shasum": "" - }, - "require": { - "pdepend/pdepend": "2.0.*", - "php": ">=5.3.0", - "symfony/config": "2.5.*", - "symfony/dependency-injection": "2.5.*", - "symfony/filesystem": "2.5.*" - }, - "bin": [ - "src/bin/phpmd" - ], - "type": "library", - "autoload": { - "psr-0": { - "PHPMD\\": "src/main/php" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Official version of PHPMD handled with Composer.", - "time": "2014-09-25 15:56:22" - }, - { - "name": "phpoption/phpoption", - "version": "1.4.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "5d099bcf0393908bf4ad69cc47dafb785d51f7f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/5d099bcf0393908bf4ad69cc47dafb785d51f7f5", - "reference": "5d099bcf0393908bf4ad69cc47dafb785d51f7f5", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-0": { - "PhpOption\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "http://jmsyst.com/", - "role": "Developer of wrapped JMSSerializerBundle" - } - ], - "description": "Option Type for PHP", - "keywords": [ - "language", - "option", - "php", - "type" - ], - "time": "2014-01-09 22:37:17" - }, - { - "name": "phpunit/php-code-coverage", - "version": "1.2.18", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", - "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "phpunit/php-file-iterator": ">=1.3.0@stable", - "phpunit/php-text-template": ">=1.2.0@stable", - "phpunit/php-token-stream": ">=1.1.3,<1.3.0" - }, - "require-dev": { - "phpunit/phpunit": "3.7.*@dev" - }, - "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.0.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "PHP/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.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" - ], - "time": "2014-09-02 10:13:14" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.3.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", - "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "File/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.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" - ], - "time": "2013-10-10 15:34:57" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "Text/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2014-01-30 17:20:04" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", - "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "PHP/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2013-08-02 07:42:54" - }, - { - "name": "phpunit/php-token-stream", - "version": "1.2.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32", - "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "classmap": [ - "PHP/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2014-03-03 05:10:30" - }, - { - "name": "phpunit/phpunit", - "version": "3.7.38", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38709dc22d519a3d1be46849868aa2ddf822bcf6", - "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=5.3.3", - "phpunit/php-code-coverage": "~1.2", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.1", - "phpunit/php-timer": "~1.0", - "phpunit/phpunit-mock-objects": "~1.2", - "symfony/yaml": "~2.0" - }, - "require-dev": { - "pear-pear.php.net/pear": "1.9.4" - }, - "suggest": { - "phpunit/php-invoker": "~1.1" - }, - "bin": [ - "composer/bin/phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.7.x-dev" - } - }, - "autoload": { - "classmap": [ - "PHPUnit/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "", - "../../symfony/yaml/" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "http://www.phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2014-10-17 09:04:17" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875", - "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "phpunit/php-text-template": ">=1.1.1@stable" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "autoload": { - "classmap": [ - "PHPUnit/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2013-01-13 10:24:48" - }, - { - "name": "pimple/pimple", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/fabpot/Pimple.git", - "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fabpot/Pimple/zipball/2019c145fe393923f3441b23f29bbdfaa5c58c4d", - "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-0": { - "Pimple": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org/", - "role": "Lead Developer" - } - ], - "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", - "homepage": "http://pimple.sensiolabs.org/", - "keywords": [ - "container", - "dependency injection" - ], - "time": "2013-11-22 08:30:29" - }, - { - "name": "psr/log", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", - "shasum": "" - }, - "type": "library", - "autoload": { - "psr-0": { - "Psr\\Log\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "time": "2012-12-21 11:40:51" - }, - { - "name": "sebastian/finder-facade", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/finder-facade.git", - "reference": "1e396fda3449fce9df032749fa4fa2619e0347e0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/1e396fda3449fce9df032749fa4fa2619e0347e0", - "reference": "1e396fda3449fce9df032749fa4fa2619e0347e0", - "shasum": "" - }, - "require": { - "symfony/finder": ">=2.2.0", - "theseer/fdomdocument": ">=1.3.1" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", - "homepage": "https://github.com/sebastianbergmann/finder-facade", - "time": "2013-05-28 06:10:03" - }, - { - "name": "sebastian/git", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/git.git", - "reference": "a99fbc102e982c1404041ef3e4d431562b29bcba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/git/zipball/a99fbc102e982c1404041ef3e4d431562b29bcba", - "reference": "a99fbc102e982c1404041ef3e4d431562b29bcba", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.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": "Simple wrapper for Git", - "homepage": "http://www.github.com/sebastianbergmann/git", - "keywords": [ - "git" - ], - "time": "2013-08-04 09:35:29" - }, - { - "name": "sebastian/phpcpd", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpcpd.git", - "reference": "a9462153f2dd90466a010179901d31fbff598365" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpcpd/zipball/a9462153f2dd90466a010179901d31fbff598365", - "reference": "a9462153f2dd90466a010179901d31fbff598365", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "phpunit/php-timer": ">=1.0.4", - "sebastian/finder-facade": ">=1.1.0", - "sebastian/version": ">=1.0.3", - "symfony/console": ">=2.2.0", - "theseer/fdomdocument": "~1.4" - }, - "bin": [ - "phpcpd" - ], - "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": "Copy/Paste Detector (CPD) for PHP code.", - "homepage": "https://github.com/sebastianbergmann/phpcpd", - "time": "2014-03-31 09:25:30" - }, - { - "name": "sebastian/version", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", - "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", - "shasum": "" - }, - "type": "library", - "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", - "time": "2014-03-07 15:35:33" - }, - { - "name": "seld/jsonlint", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "a7bc2ec9520ad15382292591b617c43bdb1fec35" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/a7bc2ec9520ad15382292591b617c43bdb1fec35", - "reference": "a7bc2ec9520ad15382292591b617c43bdb1fec35", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "bin": [ - "bin/jsonlint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be/" - } - ], - "description": "JSON Linter", - "keywords": [ - "json", - "linter", - "parser", - "validator" - ], - "time": "2014-09-05 15:36:20" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "1.5.6", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "6f3e42d311b882b25b4d409d23a289f4d3b803d5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6f3e42d311b882b25b4d409d23a289f4d3b803d5", - "reference": "6f3e42d311b882b25b4d409d23a289f4d3b803d5", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.1.2" - }, - "suggest": { - "phpunit/php-timer": "dev-master" - }, - "bin": [ - "scripts/phpcs" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-phpcs-fixer": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "CodeSniffer.php", - "CodeSniffer/CLI.php", - "CodeSniffer/Exception.php", - "CodeSniffer/File.php", - "CodeSniffer/Report.php", - "CodeSniffer/Reporting.php", - "CodeSniffer/Sniff.php", - "CodeSniffer/Tokens.php", - "CodeSniffer/Reports/", - "CodeSniffer/CommentParser/", - "CodeSniffer/Tokenizers/", - "CodeSniffer/DocGenerators/", - "CodeSniffer/Standards/AbstractPatternSniff.php", - "CodeSniffer/Standards/AbstractScopeSniff.php", - "CodeSniffer/Standards/AbstractVariableSniff.php", - "CodeSniffer/Standards/IncorrectPatternException.php", - "CodeSniffer/Standards/Generic/Sniffs/", - "CodeSniffer/Standards/MySource/Sniffs/", - "CodeSniffer/Standards/PEAR/Sniffs/", - "CodeSniffer/Standards/PSR1/Sniffs/", - "CodeSniffer/Standards/PSR2/Sniffs/", - "CodeSniffer/Standards/Squiz/Sniffs/", - "CodeSniffer/Standards/Zend/Sniffs/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenises PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", - "keywords": [ - "phpcs", - "standards" - ], - "time": "2014-12-04 22:32:15" - }, - { - "name": "symfony/config", - "version": "v2.5.8", - "target-dir": "Symfony/Component/Config", - "source": { - "type": "git", - "url": "https://github.com/symfony/Config.git", - "reference": "92f0b4c625b8c42d394b53f879d2795d84bb8c4f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Config/zipball/92f0b4c625b8c42d394b53f879d2795d84bb8c4f", - "reference": "92f0b4c625b8c42d394b53f879d2795d84bb8c4f", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "symfony/filesystem": "~2.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Config\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Config Component", - "homepage": "http://symfony.com/", - "time": "2014-12-02 20:15:53" - }, - { - "name": "symfony/console", - "version": "v2.6.1", - "target-dir": "Symfony/Component/Console", - "source": { - "type": "git", - "url": "https://github.com/symfony/Console.git", - "reference": "ef825fd9f809d275926547c9e57cbf14968793e8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/ef825fd9f809d275926547c9e57cbf14968793e8", - "reference": "ef825fd9f809d275926547c9e57cbf14968793e8", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/event-dispatcher": "~2.1", - "symfony/process": "~2.1" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/process": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Console\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Console Component", - "homepage": "http://symfony.com/", - "time": "2014-12-02 20:19:20" - }, - { - "name": "symfony/dependency-injection", - "version": "v2.5.8", - "target-dir": "Symfony/Component/DependencyInjection", - "source": { - "type": "git", - "url": "https://github.com/symfony/DependencyInjection.git", - "reference": "b4afda3c24867a17f93237ac1fcce917cc9d7695" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/DependencyInjection/zipball/b4afda3c24867a17f93237ac1fcce917cc9d7695", - "reference": "b4afda3c24867a17f93237ac1fcce917cc9d7695", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "symfony/config": "~2.2", - "symfony/expression-language": "~2.4", - "symfony/yaml": "~2.0" - }, - "suggest": { - "symfony/config": "", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\DependencyInjection\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony DependencyInjection Component", - "homepage": "http://symfony.com/", - "time": "2014-12-02 21:48:32" - }, - { - "name": "symfony/event-dispatcher", - "version": "v2.6.1", - "target-dir": "Symfony/Component/EventDispatcher", - "source": { - "type": "git", - "url": "https://github.com/symfony/EventDispatcher.git", - "reference": "720fe9bca893df7ad1b4546649473b5afddf0216" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/720fe9bca893df7ad1b4546649473b5afddf0216", - "reference": "720fe9bca893df7ad1b4546649473b5afddf0216", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.0", - "symfony/dependency-injection": "~2.6", - "symfony/expression-language": "~2.6", - "symfony/stopwatch": "~2.2" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony EventDispatcher Component", - "homepage": "http://symfony.com/", - "time": "2014-12-02 20:19:20" - }, - { - "name": "symfony/filesystem", - "version": "v2.5.8", - "target-dir": "Symfony/Component/Filesystem", - "source": { - "type": "git", - "url": "https://github.com/symfony/Filesystem.git", - "reference": "e5fc05a3a1dbb4ea0bed80fe7bd21ba3cab88c42" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Filesystem/zipball/e5fc05a3a1dbb4ea0bed80fe7bd21ba3cab88c42", - "reference": "e5fc05a3a1dbb4ea0bed80fe7bd21ba3cab88c42", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Filesystem\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Filesystem Component", - "homepage": "http://symfony.com/", - "time": "2014-12-02 20:15:53" - }, - { - "name": "symfony/finder", - "version": "v2.6.1", - "target-dir": "Symfony/Component/Finder", - "source": { - "type": "git", - "url": "https://github.com/symfony/Finder.git", - "reference": "0d3ef7f6ec55a7af5eca7914eaa0dacc04ccc721" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Finder/zipball/0d3ef7f6ec55a7af5eca7914eaa0dacc04ccc721", - "reference": "0d3ef7f6ec55a7af5eca7914eaa0dacc04ccc721", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Finder\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Finder Component", - "homepage": "http://symfony.com/", - "time": "2014-12-02 20:19:20" - }, - { - "name": "symfony/process", - "version": "v2.6.1", - "target-dir": "Symfony/Component/Process", - "source": { - "type": "git", - "url": "https://github.com/symfony/Process.git", - "reference": "bf0c9bd625f13b0b0bbe39919225cf145dfb935a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Process/zipball/bf0c9bd625f13b0b0bbe39919225cf145dfb935a", - "reference": "bf0c9bd625f13b0b0bbe39919225cf145dfb935a", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Process\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Process Component", - "homepage": "http://symfony.com/", - "time": "2014-12-02 20:19:20" - }, - { - "name": "symfony/stopwatch", - "version": "v2.6.1", - "target-dir": "Symfony/Component/Stopwatch", - "source": { - "type": "git", - "url": "https://github.com/symfony/Stopwatch.git", - "reference": "261abd360cfb6ac65ea93ffd82073e2011d034fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/261abd360cfb6ac65ea93ffd82073e2011d034fc", - "reference": "261abd360cfb6ac65ea93ffd82073e2011d034fc", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Stopwatch\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Stopwatch Component", - "homepage": "http://symfony.com/", - "time": "2014-12-02 20:19:20" - }, - { - "name": "symfony/translation", - "version": "v2.6.1", - "target-dir": "Symfony/Component/Translation", - "source": { - "type": "git", - "url": "https://github.com/symfony/Translation.git", - "reference": "5b8bf84a43317021849813f556f26dc35968156b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Translation/zipball/5b8bf84a43317021849813f556f26dc35968156b", - "reference": "5b8bf84a43317021849813f556f26dc35968156b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.0", - "symfony/intl": "~2.3", - "symfony/yaml": "~2.2" - }, - "suggest": { - "psr/log": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Translation\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Translation Component", - "homepage": "http://symfony.com/", - "time": "2014-12-02 20:19:20" - }, - { - "name": "symfony/validator", - "version": "v2.6.1", - "target-dir": "Symfony/Component/Validator", - "source": { - "type": "git", - "url": "https://github.com/symfony/Validator.git", - "reference": "4583e0321f1bcdad14d93e265eaca1001035b5c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Validator/zipball/4583e0321f1bcdad14d93e265eaca1001035b5c4", - "reference": "4583e0321f1bcdad14d93e265eaca1001035b5c4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "symfony/translation": "~2.0" - }, - "require-dev": { - "doctrine/annotations": "~1.0", - "doctrine/cache": "~1.0", - "egulias/email-validator": "~1.0", - "symfony/config": "~2.2", - "symfony/expression-language": "~2.4", - "symfony/http-foundation": "~2.1", - "symfony/intl": "~2.3", - "symfony/property-access": "~2.2", - "symfony/yaml": "~2.0" - }, - "suggest": { - "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", - "doctrine/cache": "For using the default cached annotation reader and metadata cache.", - "egulias/email-validator": "Strict (RFC compliant) email validation", - "symfony/config": "", - "symfony/expression-language": "For using the 2.4 Expression validator", - "symfony/http-foundation": "", - "symfony/intl": "", - "symfony/property-access": "For using the 2.4 Validator API", - "symfony/yaml": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Validator\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Validator Component", - "homepage": "http://symfony.com/", - "time": "2014-12-02 20:19:20" - }, - { - "name": "symfony/yaml", - "version": "v2.6.1", - "target-dir": "Symfony/Component/Yaml", - "source": { - "type": "git", - "url": "https://github.com/symfony/Yaml.git", - "reference": "3346fc090a3eb6b53d408db2903b241af51dcb20" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/3346fc090a3eb6b53d408db2903b241af51dcb20", - "reference": "3346fc090a3eb6b53d408db2903b241af51dcb20", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Yaml\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Yaml Component", - "homepage": "http://symfony.com/", - "time": "2014-12-02 20:19:20" - }, - { - "name": "tecnick.com/tcpdf", - "version": "6.2.0", - "source": { - "type": "git", - "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "40662daa766bd3a6b5eafa44dfde680ee6661716" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/40662daa766bd3a6b5eafa44dfde680ee6661716", - "reference": "40662daa766bd3a6b5eafa44dfde680ee6661716", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "fonts", - "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": [ - "LGPLv3" - ], - "authors": [ - { - "name": "Nicola Asuni", - "email": "info@tecnick.com", - "homepage": "http://nicolaasuni.tecnick.com/" - } - ], - "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" - ], - "time": "2014-12-10 18:53:49" - }, - { - "name": "theseer/fdomdocument", - "version": "1.6.0", - "source": { - "type": "git", - "url": "https://github.com/theseer/fDOMDocument.git", - "reference": "d08cf070350f884c63fc9078d27893c2ab6c7cef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/d08cf070350f884c63fc9078d27893c2ab6c7cef", - "reference": "d08cf070350f884c63fc9078d27893c2ab6c7cef", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "lib-libxml": "*", - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "lead" - } - ], - "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", - "homepage": "https://github.com/theseer/fDOMDocument", - "time": "2014-09-13 10:57:19" - }, - { - "name": "twig/twig", - "version": "v1.16.2", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "42f758d9fe2146d1f0470604fc05ee43580873fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/42f758d9fe2146d1f0470604fc05ee43580873fc", - "reference": "42f758d9fe2146d1f0470604fc05ee43580873fc", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.16-dev" - } - }, - "autoload": { - "psr-0": { - "Twig_": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org/", - "role": "Lead Developer" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - }, - { - "name": "Twig Team", - "homepage": "https://github.com/fabpot/Twig/graphs/contributors", - "role": "Contributors" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "http://twig.sensiolabs.org/", - "keywords": [ - "templating" - ], - "time": "2014-10-17 12:53:44" - }, - { - "name": "zendframework/zend-cache", - "version": "2.3.3", - "target-dir": "Zend/Cache", - "source": { - "type": "git", - "url": "https://github.com/zendframework/Component_ZendCache.git", - "reference": "1966038a1568ebeaeeeaa78ce27bc7b340e30747" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/Component_ZendCache/zipball/1966038a1568ebeaeeeaa78ce27bc7b340e30747", - "reference": "1966038a1568ebeaeeeaa78ce27bc7b340e30747", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-stdlib": "self.version" - }, - "require-dev": { - "zendframework/zend-serializer": "self.version", - "zendframework/zend-session": "self.version" - }, - "suggest": { - "ext-apc": "APC >= 3.1.6 to use the APC storage adapter", - "ext-dba": "DBA, to use the DBA storage adapter", - "ext-memcached": "Memcached >= 1.0.0 to use the Memcached storage adapter", - "ext-wincache": "WinCache, to use the WinCache storage adapter", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-session": "Zend\\Session component" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" - } - }, - "autoload": { - "psr-0": { - "Zend\\Cache\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides a generic way to cache any data", - "homepage": "https://github.com/zendframework/zf2", - "keywords": [ - "cache", - "zf2" - ], - "time": "2014-09-16 22:58:11" - }, - { - "name": "zendframework/zend-config", - "version": "2.3.3", - "target-dir": "Zend/Config", - "source": { - "type": "git", - "url": "https://github.com/zendframework/Component_ZendConfig.git", - "reference": "a9ad512e1482461a5b500ee3fcf2d06ec9c7c7e8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/Component_ZendConfig/zipball/a9ad512e1482461a5b500ee3fcf2d06ec9c7c7e8", - "reference": "a9ad512e1482461a5b500ee3fcf2d06ec9c7c7e8", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" - }, - "require-dev": { - "zendframework/zend-filter": "self.version", - "zendframework/zend-i18n": "self.version", - "zendframework/zend-json": "self.version", - "zendframework/zend-servicemanager": "self.version" - }, - "suggest": { - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes", - "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" - } - }, - "autoload": { - "psr-0": { - "Zend\\Config\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides a nested object property based user interface for accessing this configuration data within application code", - "homepage": "https://github.com/zendframework/zf2", - "keywords": [ - "config", - "zf2" - ], - "time": "2014-09-16 22:58:11" - }, - { - "name": "zendframework/zend-eventmanager", - "version": "2.3.3", - "target-dir": "Zend/EventManager", - "source": { - "type": "git", - "url": "https://github.com/zendframework/Component_ZendEventManager.git", - "reference": "4110fe64b10616b9bb71429a206d8e9e6d99e3ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/Component_ZendEventManager/zipball/4110fe64b10616b9bb71429a206d8e9e6d99e3ba", - "reference": "4110fe64b10616b9bb71429a206d8e9e6d99e3ba", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" - } - }, - "autoload": { - "psr-0": { - "Zend\\EventManager\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "homepage": "https://github.com/zendframework/zf2", - "keywords": [ - "eventmanager", - "zf2" - ], - "time": "2014-09-16 22:58:11" - }, - { - "name": "zendframework/zend-filter", - "version": "2.3.3", - "target-dir": "Zend/Filter", - "source": { - "type": "git", - "url": "https://github.com/zendframework/Component_ZendFilter.git", - "reference": "98b8c2abfdc9009e4c0157e78c9f22bf2cebb693" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/Component_ZendFilter/zipball/98b8c2abfdc9009e4c0157e78c9f22bf2cebb693", - "reference": "98b8c2abfdc9009e4c0157e78c9f22bf2cebb693", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" - }, - "require-dev": { - "zendframework/zend-crypt": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-uri": "self.version" - }, - "suggest": { - "zendframework/zend-crypt": "Zend\\Crypt component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-uri": "Zend\\Uri component for UriNormalize filter" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" - } - }, - "autoload": { - "psr-0": { - "Zend\\Filter\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides a set of commonly needed data filters", - "homepage": "https://github.com/zendframework/zf2", - "keywords": [ - "filter", - "zf2" - ], - "time": "2014-09-16 22:58:11" - }, - { - "name": "zendframework/zend-i18n", - "version": "2.3.3", - "target-dir": "Zend/I18n", - "source": { - "type": "git", - "url": "https://github.com/zendframework/Component_ZendI18n.git", - "reference": "7939bd8eaa573f10fe33a799714199ed7c1fad5c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/Component_ZendI18n/zipball/7939bd8eaa573f10fe33a799714199ed7c1fad5c", - "reference": "7939bd8eaa573f10fe33a799714199ed7c1fad5c", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" - }, - "require-dev": { - "zendframework/zend-cache": "self.version", - "zendframework/zend-config": "self.version", - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-filter": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-validator": "self.version", - "zendframework/zend-view": "self.version" - }, - "suggest": { - "ext-intl": "Required for most features of Zend\\I18n; included in default builds of PHP", - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-eventmanager": "You should install this package to use the events in the translator", - "zendframework/zend-filter": "You should install this package to use the provided filters", - "zendframework/zend-resources": "Translation resources", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "You should install this package to use the provided validators", - "zendframework/zend-view": "You should install this package to use the provided view helpers" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" - } - }, - "autoload": { - "psr-0": { - "Zend\\I18n\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "homepage": "https://github.com/zendframework/zf2", - "keywords": [ - "i18n", - "zf2" - ], - "time": "2014-09-16 22:58:11" - }, - { - "name": "zendframework/zend-json", - "version": "2.3.3", - "target-dir": "Zend/Json", - "source": { - "type": "git", - "url": "https://github.com/zendframework/Component_ZendJson.git", - "reference": "4093e5a0a166a5d02532bac6e5671a7b21d203b5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/Component_ZendJson/zipball/4093e5a0a166a5d02532bac6e5671a7b21d203b5", - "reference": "4093e5a0a166a5d02532bac6e5671a7b21d203b5", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" - }, - "require-dev": { - "zendframework/zend-http": "self.version", - "zendframework/zend-server": "self.version" - }, - "suggest": { - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-server": "Zend\\Server component" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" - } - }, - "autoload": { - "psr-0": { - "Zend\\Json\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", - "homepage": "https://github.com/zendframework/zf2", - "keywords": [ - "json", - "zf2" - ], - "time": "2014-09-16 22:58:11" - }, - { - "name": "zendframework/zend-math", - "version": "2.3.3", - "target-dir": "Zend/Math", - "source": { - "type": "git", - "url": "https://github.com/zendframework/Component_ZendMath.git", - "reference": "a197ee44ade44a289f0f250c2aedb321b3618573" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/Component_ZendMath/zipball/a197ee44ade44a289f0f250c2aedb321b3618573", - "reference": "a197ee44ade44a289f0f250c2aedb321b3618573", - "shasum": "" - }, - "require": { - "php": ">=5.3.23" - }, - "suggest": { - "ext-bcmath": "If using the bcmath functionality", - "ext-gmp": "If using the gmp functionality", - "ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if OpenSSL/Mcrypt extensions are unavailable", - "zendframework/zend-servicemanager": ">= current version, if using the BigInteger::factory functionality" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" - } - }, - "autoload": { - "psr-0": { - "Zend\\Math\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "homepage": "https://github.com/zendframework/zf2", - "keywords": [ - "math", - "zf2" - ], - "time": "2014-09-16 22:58:11" - }, - { - "name": "zendframework/zend-serializer", - "version": "2.3.3", - "target-dir": "Zend/Serializer", - "source": { - "type": "git", - "url": "https://github.com/zendframework/Component_ZendSerializer.git", - "reference": "34ee4925e7e256bfa80c4c3dcc8e764d02a51edd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/Component_ZendSerializer/zipball/34ee4925e7e256bfa80c4c3dcc8e764d02a51edd", - "reference": "34ee4925e7e256bfa80c4c3dcc8e764d02a51edd", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-json": "self.version", - "zendframework/zend-math": "self.version", - "zendframework/zend-stdlib": "self.version" - }, - "require-dev": { - "zendframework/zend-servicemanager": "self.version" - }, - "suggest": { - "zendframework/zend-servicemanager": "To support plugin manager support" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" - } - }, - "autoload": { - "psr-0": { - "Zend\\Serializer\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides an adapter based interface to simply generate storable representation of PHP types by different facilities, and recover", - "homepage": "https://github.com/zendframework/zf2", - "keywords": [ - "serializer", - "zf2" - ], - "time": "2014-09-16 22:58:11" - }, - { - "name": "zendframework/zend-servicemanager", - "version": "2.3.3", - "target-dir": "Zend/ServiceManager", - "source": { - "type": "git", - "url": "https://github.com/zendframework/Component_ZendServiceManager.git", - "reference": "559403e4fd10db2516641f20f129a568d7e6a993" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/Component_ZendServiceManager/zipball/559403e4fd10db2516641f20f129a568d7e6a993", - "reference": "559403e4fd10db2516641f20f129a568d7e6a993", - "shasum": "" - }, - "require": { - "php": ">=5.3.23" - }, - "require-dev": { - "zendframework/zend-di": "self.version" - }, - "suggest": { - "zendframework/zend-di": "Zend\\Di component" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" - } - }, - "autoload": { - "psr-0": { - "Zend\\ServiceManager\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "homepage": "https://github.com/zendframework/zf2", - "keywords": [ - "servicemanager", - "zf2" - ], - "time": "2014-09-16 22:58:11" - }, - { - "name": "zendframework/zend-stdlib", - "version": "2.3.3", - "target-dir": "Zend/Stdlib", - "source": { - "type": "git", - "url": "https://github.com/zendframework/Component_ZendStdlib.git", - "reference": "fa33e6647f830d0d2a1cb451efcdfe1bb9a66c33" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/Component_ZendStdlib/zipball/fa33e6647f830d0d2a1cb451efcdfe1bb9a66c33", - "reference": "fa33e6647f830d0d2a1cb451efcdfe1bb9a66c33", - "shasum": "" - }, - "require": { - "php": ">=5.3.23" - }, - "require-dev": { - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-serializer": "self.version", - "zendframework/zend-servicemanager": "self.version" - }, - "suggest": { - "zendframework/zend-eventmanager": "To support aggregate hydrator usage", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-servicemanager": "To support hydrator plugin manager usage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" - } - }, - "autoload": { - "psr-0": { - "Zend\\Stdlib\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "homepage": "https://github.com/zendframework/zf2", - "keywords": [ - "stdlib", - "zf2" - ], - "time": "2014-09-16 22:58:11" - }, - { - "name": "zetacomponents/base", - "version": "1.9", - "source": { - "type": "git", - "url": "https://github.com/zetacomponents/Base.git", - "reference": "f20df24e8de3e48b6b69b2503f917e457281e687" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zetacomponents/Base/zipball/f20df24e8de3e48b6b69b2503f917e457281e687", - "reference": "f20df24e8de3e48b6b69b2503f917e457281e687", - "shasum": "" - }, - "require-dev": { - "zetacomponents/unit-test": "*" - }, - "type": "library", - "autoload": { - "classmap": [ - "src" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Sergey Alexeev" - }, - { - "name": "Sebastian Bergmann" - }, - { - "name": "Jan Borsodi" - }, - { - "name": "Raymond Bosman" - }, - { - "name": "Frederik Holljen" - }, - { - "name": "Kore Nordmann" - }, - { - "name": "Derick Rethans" - }, - { - "name": "Vadym Savchuk" - }, - { - "name": "Tobias Schlitt" - }, - { - "name": "Alexandru Stanoi" - } - ], - "description": "The Base package provides the basic infrastructure that all packages rely on. Therefore every component relies on this package.", - "homepage": "https://github.com/zetacomponents", - "time": "2014-09-19 03:28:34" - }, - { - "name": "zetacomponents/document", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/zetacomponents/Document.git", - "reference": "688abfde573cf3fe0730f82538fbd7aa9fc95bc8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zetacomponents/Document/zipball/688abfde573cf3fe0730f82538fbd7aa9fc95bc8", - "reference": "688abfde573cf3fe0730f82538fbd7aa9fc95bc8", - "shasum": "" - }, - "require": { - "zetacomponents/base": "*" - }, - "require-dev": { - "zetacomponents/unit-test": "dev-master" - }, - "type": "library", - "autoload": { - "classmap": [ - "src" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Sebastian Bergmann" - }, - { - "name": "Kore Nordmann" - }, - { - "name": "Derick Rethans" - }, - { - "name": "Tobias Schlitt" - }, - { - "name": "Alexandru Stanoi" - } - ], - "description": "The Document components provides a general conversion framework for different semantic document markup languages like XHTML, Docbook, RST and similar.", - "homepage": "https://github.com/zetacomponents", - "time": "2013-12-19 11:40:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "platform": { - "php": ">=5.3.3", - "ext-xml": "*" - }, - "platform-dev": [] -} diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 5631b06028..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..5daf85dbfd --- /dev/null +++ b/docs/changes/1.x/1.4.0.md @@ -0,0 +1,56 @@ +# [1.4.0](https://github.com/PHPOffice/PHPWord/tree/1.4.0) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/1.3.0...1.4.0) + +## Enhancements + +- Default Font: Allow specify Asisn font and Latin font separately + +- 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) +- Writer Word2007: Support for padding in Table Cell by [@Azamat8405](https://github.com/Azamat8405) in [#2697](https://github.com/PHPOffice/PHPWord/pull/2697) +- Added support for PHP 8.4 by [@Progi1984](https://github.com/Progi1984) in [#2660](https://github.com/PHPOffice/PHPWord/pull/2660) +- Autoload : Allow to use PHPWord without Composer fixing [#2543](https://github.com/PHPOffice/PHPWord/issues/2543), [#2552](https://github.com/PHPOffice/PHPWord/issues/2552), [#2716](https://github.com/PHPOffice/PHPWord/issues/2716), [#2717](https://github.com/PHPOffice/PHPWord/issues/2717) in [#2722](https://github.com/PHPOffice/PHPWord/pull/2722) +- Add Default font color for Word by [@Collie-IT](https://github.com/Collie-IT) in [#2700](https://github.com/PHPOffice/PHPWord/pull/2700) +- Writer HTML: Support Default font color by [@MichaelPFrey](https://github.com/MichaelPFrey) in [#2731](https://github.com/PHPOffice/PHPWord/pull/2731) +- Writer ODText: Support Default font color by [@MichaelPFrey](https://github.com/MichaelPFrey) in [#2735](https://github.com/PHPOffice/PHPWord/pull/2735) +- Add basic ruby text (phonetic guide) support for Word2007 and HTML Reader/Writer, RTF Writer, basic support for ODT writing by [@Deadpikle](https://github.com/Deadpikle) in [#2727](https://github.com/PHPOffice/PHPWord/pull/2727) +- Reader HTML: Support font styles for h1/h6 by [@Progi1984](https://github.com/Progi1984) fixing [#2619](https://github.com/PHPOffice/PHPWord/issues/2619) in [#2737](https://github.com/PHPOffice/PHPWord/pull/2737) +- Writer EPub3: Basic support by [@Sambit003](https://github.com/Sambit003) fixing [#55](https://github.com/PHPOffice/PHPWord/issues/55) in [#2724](https://github.com/PHPOffice/PHPWord/pull/2724) +- Writer Word2007: Added support for background and border color transparency in Text Box element by [@chudy20007](https://github.com/Chudy20007) in [#2555](https://github.com/PHPOffice/PHPWord/pull/2555) +- Writer/Reader Word2007: Added support for the firstLineChars for Indentation by [@liuzhilinux](https://github.com/liuzhilinux) & [@Progi1984](https://github.com/Progi1984) in [#2753](https://github.com/PHPOffice/PHPWord/pull/2753) + +### 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) +- Reader HTML: Support for differents size units for table by [@Progi1984](https://github.com/Progi1984) fixing [#2384](https://github.com/PHPOffice/PHPWord/issues/2384), [#2701](https://github.com/PHPOffice/PHPWord/issues/2701) in [#2725](https://github.com/PHPOffice/PHPWord/pull/2725) +- Reader Word2007: Respect paragraph indent units by [@tugmaks](https://github.com/tugmaks) & [@Progi1984](https://github.com/Progi1984) fixing [#507](https://github.com/PHPOffice/PHPWord/issues/507) in [#2726](https://github.com/PHPOffice/PHPWord/pull/2726) +- Reader Word2007: Support Header elements within Title elements by [@SpraxDev](https://github.com/SpraxDev) fixing [#2616](https://github.com/PHPOffice/PHPWord/issues/2616), [#2426](https://github.com/PHPOffice/PHPWord/issues/2426) in [#2674](https://github.com/PHPOffice/PHPWord/pull/2674) +- Reader HTML: Support for inherit value for property line-height by [@Progi1984](https://github.com/Progi1984) fixing [#2683](https://github.com/PHPOffice/PHPWord/issues/2683) in [#2733](https://github.com/PHPOffice/PHPWord/pull/2733) +- Writer HTML: Fixed null string for Text Elements by [@armagedon007](https://github.com/armagedon007) and [@Progi1984](https://github.com/Progi1984) in [#2738](https://github.com/PHPOffice/PHPWord/pull/2738) +- Template Processor: Fix 0 considered as empty string by [@cavasinf](https://github.com/cavasinf), [@SnipsMine](https://github.com/SnipsMine) and [@Progi1984](https://github.com/Progi1984) fixing [#2572](https://github.com/PHPOffice/PHPWord/issues/2572), [#2703](https://github.com/PHPOffice/PHPWord/issues/2703) in [#2748](https://github.com/PHPOffice/PHPWord/pull/2748) +- Word2007 Writer : Corrected generating TOC to fix page number missing issues [@jgiacomello](https://github.com/jgiacomello) in [#2556](https://github.com/PHPOffice/PHPWord/pull/2556) +- Enhanced error handling in encoding functions [@michalschroeder](https://github.com/michalschroeder) in [#2784](https://github.com/PHPOffice/PHPWord/pull/2784) + +### 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) +- Adding the possibility to use iterate search and replace with setValues by [@moghwan](https://github.com/moghwan) in [#2632](https://github.com/PHPOffice/PHPWord/pull/2640) +- Add test cases that test the ODTText and Word2007 reader using the corresponding writer, increasing test coverage by [@MichaelPFrey](https://github.com/MichaelPFrey) in [#2745](https://github.com/PHPOffice/PHPWord/pull/2745) +- Updated TOCTest to use static HTML content instead of external resource [@michalschroeder](https://github.com/michalschroeder) in [#2784](https://github.com/PHPOffice/PHPWord/pull/2784) +- Bump PHPOffice/math from 0.2.0 to 0.3.0 by [@eileenmcnaughton](https://github.com/eileenmcnaughton) in [#2785](https://github.com/PHPOffice/PHPWord/pull/2785) + +### Deprecations +- Deprecate `PhpOffice\PhpWord\Style\Paragraph::getIndent()` : Use `PhpOffice\PhpWord\Style\Paragraph::getIndentLeft()` +- Deprecate `PhpOffice\PhpWord\Style\Paragraph::setHanging()` : Use `PhpOffice\PhpWord\Style\Paragraph::setIndentHanging()` +- Deprecate `PhpOffice\PhpWord\Style\Paragraph::setIndent()` : Use `PhpOffice\PhpWord\Style\Paragraph::setIndentLeft()` + +### BC Breaks + +### Notes +- Writer ODText previously used to set 'style:use-window-font-color' to 'true', now it is set to 'false'. (see [#2735](https://github.com/PHPOffice/PHPWord/pull/2735)) + The effect of this attribute is "implementation dependent" (if implemented at all). + Setting it to false allows setting a default font color and improves interoperabilt, + but may break certain specific use cases. diff --git a/docs/changes/1.x/1.5.0.md b/docs/changes/1.x/1.5.0.md new file mode 100644 index 0000000000..b96865bada --- /dev/null +++ b/docs/changes/1.x/1.5.0.md @@ -0,0 +1,19 @@ +# [1.5.0](https://github.com/PHPOffice/PHPWord/tree/1.5.0) (WIP) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/1.4.0...1.5.0) + +## Enhancements + +### Bug fixes + +- Set writeAttribute return type by [@radarhere](https://github.com/radarhere) fixing [#2204](https://github.com/PHPOffice/PHPWord/issues/2204) in [#2776](https://github.com/PHPOffice/PHPWord/pull/2776) + +### Miscellaneous + +- Update phpstan/phpstan requirement from ^0.12.88 || ^1.0.0 to ^0.12.88 || ^1.0.0 || ^2.0.0 by [@dependabot](https://github.com/dependabot) & [@Progi1984](https://github.com/Progi1984) in [#2736](https://github.com/PHPOffice/PHPWord/pull/2736) + +### Deprecations + +### BC Breaks + +### Notes \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index de645350d0..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, 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.12.0' -# 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 addSection($sectionStyle); - -The ``$sectionStyle`` is an optional associative array that sets the -section. Example: - -.. code-block:: php - - $sectionStyle = array( - 'orientation' => 'landscape', - 'marginTop' => 600, - 'colsNum' => 2, - ); - -Page number -~~~~~~~~~~~ - -You can change a section page number by using the ``pageNumberingStart`` -style of the section. - -.. code-block:: php - - // Method 1 - $section = $phpWord->addSection(array('pageNumberingStart' => 1)); - - // Method 2 - $section = $phpWord->addSection(); - $section->getStyle()->setPageNumberingStart(1); - -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 - - // Method 1 - $section = $phpWord->addSection(array('breakType' => 'continuous', 'colsNum' => 2)); - - // Method 2 - $section = $phpWord->addSection(); - $section->getStyle()->setBreakType('continuous'); - $section->getStyle()->setColsNum(2); - -Line numbering -~~~~~~~~~~~~~~ - -You can apply line numbering to a section by using the ``lineNumbering`` -style of the section. - -.. code-block:: php - - // Method 1 - $section = $phpWord->addSection(array('lineNumbering' => array())); - - // Method 2 - $section = $phpWord->addSection(); - $section->getStyle()->setLineNumbering(array()); - -Below are the properties of the line numbering style. - -- ``start`` Line numbering starting value -- ``increment`` Line number increments -- ``distance`` Distance between text and line numbering in twip -- ``restart`` Line numbering restart setting - continuous\|newPage\|newSection - -Headers -------- - -Each section can have its own header reference. To create a header use -the ``addHeader`` method: - -.. code-block:: php - - $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. -Additionally, only inside of the header reference you can add watermarks -or background pictures. See "Watermarks" section. - -Footers -------- - -Each section can have its own footer reference. To create a footer, use -the ``addFooter`` method: - -.. code-block:: php - - $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: - -- Texts ``addText`` and ``createTextrun`` -- Text breaks -- Images -- Tables -- Preserve text - -See the "Elements" section for the detail of each elements. - -Other containers ----------------- - -Textruns, table cells, and footnotes are elements that can also act as -containers. See the corresponding "Elements" section for the detail of -each elements. diff --git a/docs/credits.md b/docs/credits.md new file mode 100644 index 0000000000..66540be002 --- /dev/null +++ b/docs/credits.md @@ -0,0 +1,40 @@ +# Credits + +Images from chart page come from the [LibreOffice Core](https://github.com/LibreOffice/core/tree/master/icon-themes/galaxy/chart2/res). + +Some definitions come from the [Office Open XML](http://officeopenxml.com). + +## References + +### OpenXML + +Known as "ISO/IEC 29500, Third edition, 2012-09-01" + +ISO : + +- [Part 1: Fundamentals and Markup Language Reference](http://standards.iso.org/ittf/PubliclyAvailableStandards/c061750_ISO_IEC_29500-1_2012.zip) +- [Part 2: Open Packaging Conventions](http://standards.iso.org/ittf/PubliclyAvailableStandards/c061796_ISO_IEC_29500-2_2012.zip) +- [Part 3: Markup Compatibility and Extensibility](http://standards.iso.org/ittf/PubliclyAvailableStandards/c061797_ISO_IEC_29500-3_2012.zip) +- [Part 4: Transitional Migration Features](http://standards.iso.org/ittf/PubliclyAvailableStandards/c061798_ISO_IEC_29500-4_2012.zip) + +MSDN : + +- [Open XML SDK 2.5 with Validator](http://www.microsoft.com/en-gb/download/details.aspx?id=30425) +- [DocumentFormat.OpenXml.Wordprocessing Namespace on MSDN](http://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing%28v=office.14%29.aspx) + +Library of Congress : + +- [OOXML Format Family -- ISO/IEC 29500 and ECMA 376](https://www.loc.gov/preservation/digital/formats/fdd/fdd000395.shtml) +- [Schemas in W3C XML Schema language and in RELAX NG for the Strict variant of PPTX, etc.](http://standards.iso.org/ittf/PubliclyAvailableStandards/c071691_ISO_IEC_29500-1_2016_Electronic_inserts.zip) + + +### OpenDocument + +- [Oasis OpenDocument Standard Version 1.2](http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os.html) +- [Schema Central Open Document 1.1](http://www.datypic.com/sc/odf/ss.html) + +### Rich Text Format + +- [Rich Text Format (RTF) Specification, version 1.9.1](http://www.microsoft.com/en-us/download/details.aspx?id=10725) + +### Word 97 \ No newline at end of file diff --git a/docs/credits.rst b/docs/credits.rst deleted file mode 100644 index 9b6e46c68c..0000000000 --- a/docs/credits.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. _credits: - -Credits -======= diff --git a/docs/elements.rst b/docs/elements.rst deleted file mode 100644 index eb70f9ad9b..0000000000 --- a/docs/elements.rst +++ /dev/null @@ -1,512 +0,0 @@ -.. _elements: - -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 | v | v | v | v | v | v | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 2 | Text Run | v | v | v | v | - | - | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 3 | Link | v | v | v | v | v | v | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 4 | Title | v | ? | ? | ? | ? | ? | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 5 | Preserve Text | ? | v | v | v\* | - | - | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 6 | Text Break | v | v | v | v | v | v | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 7 | Page Break | v | - | - | - | - | - | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 8 | List | v | v | v | v | - | - | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 9 | Table | v | v | v | v | - | - | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 10 | Image | v | v | v | v | v | v | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 11 | Watermark | - | v | - | - | - | - | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 12 | Object | v | v | v | v | v | v | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 13 | TOC | v | - | - | - | - | - | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 14 | Footnote | v | - | - | v\*\* | v\*\* | - | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 15 | Endnote | v | - | - | v\*\* | v\*\* | - | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 16 | CheckBox | v | v | v | v | - | - | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 17 | TextBox | v | v | v | v | - | - | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 18 | Field | v | v | v | v | v | v | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 19 | Line | v | v | v | v | v | v | -+-------+-----------------+-----------+----------+----------+---------+------------+------------+ - -Legend: - -- ``v`` Available -- ``v*`` Available only when inside header/footer -- ``v**`` Available only when inside section -- ``-`` Not available -- ``?`` Should be available - -Texts ------ - -Text can be added by using ``addText`` and ``addTextRun`` method. -``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: - -.. code-block:: php - - $section->addText($text, [$fontStyle], [$paragraphStyle]); - $textrun = $section->addTextRun([$paragraphStyle]); - -Text styles -~~~~~~~~~~~ - -You can use the ``$fontStyle`` and ``$paragraphStyle`` variable to -define text formatting. There are 2 options to style the inserted text -elements, i.e. inline style by using array or defined style by adding -style definition. - -Inline style examples: - -.. code-block:: php - - $fontStyle = array('name' => 'Times New Roman', 'size' => 9); - $paragraphStyle = array('align' => 'both'); - $section->addText('I am simple paragraph', $fontStyle, $paragraphStyle); - - $textrun = $section->addTextRun(); - $textrun->addText('I am bold', array('bold' => true)); - $textrun->addText('I am italic', array('italic' => true)); - $textrun->addText('I am colored', array('color' => 'AACC00')); - -Defined style examples: - -.. code-block:: php - - $fontStyle = array('color' => '006699', 'size' => 18, 'bold' => true); - $phpWord->addFontStyle('fStyle', $fontStyle); - $text = $section->addText('Hello world!', 'fStyle'); - - $paragraphStyle = array('align' => 'center'); - $phpWord->addParagraphStyle('pStyle', $paragraphStyle); - $text = $section->addText('Hello world!', 'pStyle'); - -Font style -^^^^^^^^^^ - -Available font styles: - -- ``name`` Font name, e.g. *Arial* -- ``size`` Font size, e.g. *20*, *22*, -- ``hint`` Font content type, *default*, *eastAsia*, or *cs* -- ``bold`` Bold, *true* or *false* -- ``italic`` Italic, *true* or *false* -- ``superScript`` Superscript, *true* or *false* -- ``subScript`` Subscript, *true* or *false* -- ``underline`` Underline, *dash*, *dotted*, etc. -- ``strikethrough`` Strikethrough, *true* or *false* -- ``doubleStrikethrough`` Double strikethrough, *true* or *false* -- ``color`` Font color, e.g. *FF0000* -- ``fgColor`` Font highlight color, e.g. *yellow*, *green*, *blue* -- ``bgColor`` Font background color, e.g. *FF0000* -- ``smallCaps`` Small caps, *true* or *false* -- ``allCaps`` All caps, *true* or *false* - -Paragraph style -^^^^^^^^^^^^^^^ - -Available paragraph styles: - -- ``align`` Paragraph alignment, *left*, *right* or *center* -- ``spaceBefore`` Space before paragraph -- ``spaceAfter`` Space after paragraph -- ``indent`` Indent by how much -- ``hanging`` Hanging by how much -- ``basedOn`` Parent style -- ``next`` Style for next paragraph -- ``widowControl`` Allow first/last line to display on a separate page, - *true* or *false* -- ``keepNext`` Keep paragraph with next paragraph, *true* or *false* -- ``keepLines`` Keep all lines on one page, *true* or *false* -- ``pageBreakBefore`` Start paragraph on next page, *true* or *false* -- ``lineHeight`` text line height, e.g. *1.0*, *1.5*, ect... -- ``tabs`` Set of custom tab stops - -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. - -.. code-block:: php - - $phpWord->addTitleStyle($depth, [$fontStyle], [$paragraphStyle]); - $section->addTitle($text, [$depth]); - -Its 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 "Font style" section. -- ``$paragraphStyle`` See "Paragraph style" section. - -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 paramaters are optional. - -.. code-block:: php - - $section->addTextBreak([$breakCount], [$fontStyle], [$paragraphStyle]); - -- ``$breakCount`` How many lines -- ``$fontStyle`` See "Font style" section. -- ``$paragraphStyle`` See "Paragraph style" section. - -Page breaks -~~~~~~~~~~~ - -There are two ways to insert a page breaks, using the ``addPageBreak`` -method or using the ``pageBreakBefore`` style of paragraph. - -:: code-block:: php - - \\$section->addPageBreak(); - -Lists ------ - -To add a list item use the function ``addListItem``. - -Basic usage: - -.. code-block:: php - - $section->addListItem($text, [$depth], [$fontStyle], [$listStyle], [$paragraphStyle]); - -Parameters: - -- ``$text`` Text that appears in the document. -- ``$depth`` Depth of list item. -- ``$fontStyle`` See "Font style" section. -- ``$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 "Paragraph style" section. - -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'); - -Level styles: - -- ``start`` Starting value -- ``format`` Numbering format - bullet\|decimal\|upperRoman\|lowerRoman\|upperLetter\|lowerLetter -- ``restart`` Restart numbering level symbol -- ``suffix`` Content between numbering symbol and paragraph text - tab\|space\|nothing -- ``text`` Numbering level text e.g. %1 for nonbullet or bullet - character -- ``align`` Numbering symbol align left\|center\|right\|both -- ``left`` See paragraph style -- ``hanging`` See paragraph style -- ``tabPos`` See paragraph style -- ``font`` Font name -- ``hint`` See font 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'); - -Table, row, and cell styles -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Table styles: - -- ``width`` Table width in percent -- ``bgColor`` Background color, e.g. '9966CC' -- ``border(Top|Right|Bottom|Left)Size`` Border size in twips -- ``border(Top|Right|Bottom|Left)Color`` Border color, e.g. '9966CC' -- ``cellMargin(Top|Right|Bottom|Left)`` Cell margin in twips - -Row styles: - -- ``tblHeader`` Repeat table row on every new page, *true* or *false* -- ``cantSplit`` Table row cannot break across pages, *true* or *false* -- ``exactHeight`` Row height is exact or at least - -Cell styles: - -- ``width`` Cell width in twips -- ``valign`` Vertical alignment, *top*, *center*, *both*, *bottom* -- ``textDirection`` Direction of text -- ``bgColor`` Background color, e.g. '9966CC' -- ``border(Top|Right|Bottom|Left)Size`` Border size in twips -- ``border(Top|Right|Bottom|Left)Color`` Border color, e.g. '9966CC' -- ``gridSpan`` Number of columns spanned -- ``vMerge`` *restart* or *continue* - -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]); - -- source String path to a local image or URL of a remote image -- styles Array fo styles for the image. See below. - -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'); - -Image styles -~~~~~~~~~~~~ - -Available image styles: - -- ``width`` Width in pixels -- ``height`` Height in pixels -- ``align`` Image alignment, *left*, *right*, or *center* -- ``marginTop`` Top margin in inches, can be negative -- ``marginLeft`` Left margin in inches, can be negative -- ``wrappingStyle`` Wrapping style, *inline*, *square*, *tight*, - *behind*, or *infront* - -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 ``addObject`` method. - -.. code-block:: php - - $section->addObject($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 PHPWord\_Style\_TOC. -- ``tabPos`` The position of the tab where the page number appears in - twips. -- ``indent`` The indent factor of the titles in twips. - -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``, ``addObject`` 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.'); - -The footnote reference number will be displayed with decimal number -starting from 1. This number use ``FooterReference`` style which you can -redefine by ``addFontStyle`` method. Default value for this style is -``array('superScript' => true)``; - -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 following the check box -- ``$fontStyle`` See "Font style" section. -- ``$paragraphStyle`` See "Paragraph style" section. - -Textboxes ---------- - -To be completed - -Fields ------- - -To be completed - -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 twips -- ``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, ovel -- ``width`` Line-object width in pt -- ``height`` Line-object height in pt -- ``flip`` Flip the line element: true, false 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 79652ae025..0000000000 --- a/docs/faq.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. _faq: - -Frequently asked questions -========================== - -How contribute to PHPWord ? ---------------------------- - -- Improve the documentation (`Sphinx Format `__) - - -Is this the same with PHPWord that I found in CodePlex? -------------------------------------------------------- - -No. This one is much better with tons of new features that you can’t -find in PHPWord 0.6.3. The development in CodePlex is halted and -switched to GitHub to allow more participation from the crowd. The more -the merrier, right? - -I’ve been running PHPWord from CodePlex flawlessly, but I can’t use the latest PHPWord from GitHub. Why? --------------------------------------------------------------------------------------------------------- - -PHPWord requires PHP 5.3+ since 0.8, while PHPWord 0.6.3 from CodePlex -can run with PHP 5.2. There’s a lot of new features that we can get from -PHP 5.3 and it’s been around since 2009! You should upgrade your PHP -version to use PHPWord 0.8+. diff --git a/docs/general.rst b/docs/general.rst deleted file mode 100644 index 34d3af2477..0000000000 --- a/docs/general.rst +++ /dev/null @@ -1,181 +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( - htmlspecialchars( - '"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( - htmlspecialchars( - '"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( - htmlspecialchars( - '"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( - htmlspecialchars('"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. */ - -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 -included with PHPWord. - -.. code-block:: php - - \PhpOffice\PhpWord\Settings::setZipClass(\PhpOffice\PhpWord\Settings::PCLZIP); - -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 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 twips. - -.. 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)); - diff --git a/docs/howto.md b/docs/howto.md new file mode 100644 index 0000000000..fd4c860682 --- /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(__DIR__ . '/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..4e485bf739 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,70 @@ +# 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) +* extract the package and put the contents to your machine. + + +``` php +`__ extension -- PHP `XML - Parser `__ - extension - -Optional PHP extensions: - -- `GD `__ -- `XMLWriter `__ -- `XSL `__ - -Installation ------------- - -There are two ways to install PHPWord, i.e. via -`Composer `__ or manually by downloading the -library. - -Using Composer -~~~~~~~~~~~~~~ - -To install via Composer, add the following lines to your -``composer.json``: - -.. code-block:: json - - { - "require": { - "phpoffice/phpword": "dev-master" - } - } - -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. - -.. code-block:: json - - { - "require": { - "phpoffice/phpword": "dev-develop" - } - } - - -Manual install -~~~~~~~~~~~~~~ - -To install manually, you change to the webserver directory of your file system . -Then you have 2 possibilities. - - 1. `download PHPWord package from github `__. - Extract the package and put the contents to your machine. - 2. Alternatively you can use Git to install it: - .. code-block:: console - - git clone https://github.com/PHPOffice/PHPWord.git - -To use the library, include ``src/PhpWord/Autoloader.php`` in your PHP script and -invoke ``Autoloader::register``. - -.. code-block:: php - - require_once '/path/to/src/PhpWord/Autoloader.php'; - \PhpOffice\PhpWord\Autoloader::register(); - - -Using samples -------------- - -After installation, you can browse and use the samples that we've -provided, either by command line or using browser. If you can access -your PHPWord library folder using browser, point your browser to the -``samples`` folder, e.g. ``http://localhost/PhpWord/samples/``. diff --git a/docs/intro.rst b/docs/intro.rst deleted file mode 100644 index d2decd7c94..0000000000 --- a/docs/intro.rst +++ /dev/null @@ -1,195 +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) -- Create document from templates -- Use XSL 1.0 style sheets to transform main document part of OOXML - template -- ... and many more features on progress - -File formats ------------- - -Below are the supported features for each file formats. - -Writers -~~~~~~~ - -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| Features | | DOCX | ODT | 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 | ✓ | | | ✓ | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| **Graphs** | 2D basic graphs | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | 2D advanced graphs | | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | 3D graphs | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| **Math** | OMML support | | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | MathML support | | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| **Bonus** | Encryption | | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Protection | | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ - -Readers -~~~~~~~ - -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| Features | | DOCX | DOC | ODT | 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 | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| **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 0be6b4e06a..0000000000 --- a/docs/recipes.rst +++ /dev/null @@ -1,98 +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 20aa1ed061..0000000000 --- a/docs/references.rst +++ /dev/null @@ -1,30 +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/src/documentation.md b/docs/src/documentation.md deleted file mode 100644 index 1a59e94230..0000000000 --- a/docs/src/documentation.md +++ /dev/null @@ -1,1134 +0,0 @@ - -# Contents - -- [Introduction](#introduction) - - [Features](#features) - - [File formats](#file-formats) -- [Installing/configuring](#installing-configuring) - - [Requirements](#requirements) - - [Installation](#installation) - - [Using samples](#using-samples) -- [General usage](#general-usage) - - [Basic example](#basic-example) - - [Settings](#settings) - - [Default font](#default-font) - - [Document properties](#document-properties) - - [Measurement units](#measurement-units) -- [Containers](#containers) - - [Sections](#sections) - - [Headers](#headers) - - [Footers](#footers) - - [Other containers](#other-containers) -- [Elements](#elements) - - [Texts](#texts) - - [Breaks](#breaks) - - [Lists](#lists) - - [Tables](#tables) - - [Images](#images) - - [Objects](#objects) - - [Table of contents](#table-of-contents) - - [Footnotes & endnotes](#footnotes-endnotes) - - [Checkboxes](#checkboxes) - - [Textboxes](#textboxes) - - [Fields](#fields) - - [Lines](#lines) - - [Shapes](#shapes) - - [Charts](#charts) - - [FormFields](#form-fields) -- [Styles](#styles) - - [Section](#section) - - [Font](#font) - - [Paragraph](#paragraph) - - [Table](#table) -- [Templates processing](#templates-processing) -- [Writers & readers](#writers-readers) - - [OOXML](#ooxml) - - [OpenDocument](#opendocument) - - [RTF](#rtf) - - [HTML](#html) - - [PDF](#pdf) -- [Recipes](#recipes) -- [Frequently asked questions](#frequently-asked-questions) -- [References](#references) - -# 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](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), and [Rich Text Format](http://en.wikipedia.org/wiki/Rich_Text_Format) (RTF). - -PHPWord is an open source project licensed under the terms of [LGPL version 3](https://github.com/PHPOffice/PHPWord/blob/develop/COPYING.LESSER). PHPWord is aimed to be a high quality software product by incorporating [continuous integration](https://travis-ci.org/PHPOffice/PHPWord) and [unit testing](http://phpoffice.github.io/PHPWord/coverage/develop/). You can learn more about PHPWord by reading this Developers' Documentation and the [API Documentation](http://phpoffice.github.io/PHPWord/docs/develop/). - -## 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 main document part of OOXML template -- ... and many more features on progress - -## File formats - -Below are the supported features for each file formats. - -### Writers - -| Features | | DOCX | ODT | 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 | ✓ | | | ✓ | | -| **Graphs** | 2D basic graphs | ✓ | | | | | -| | 2D advanced graphs | | | | | | -| | 3D graphs | ✓ | | | | | -| **Math** | OMML support | | | | | | -| | MathML support | | | | | | -| **Bonus** | Encryption | | | | | | -| | Protection | | | | | | - -### Readers - -| Features | | DOCX | ODT | 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 | ✓ | | | | -| **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](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 [develop](https://github.com/PHPOffice/PHPWord/tree/develop) branch -- Submit [bug reports or feature requests](https://github.com/PHPOffice/PHPWord/issues) to GitHub -- Follow [@PHPWord](https://twitter.com/PHPWord) and [@PHPOffice](https://twitter.com/PHPOffice) on Twitter - -# Installing/configuring - -## Requirements - -Mandatory: - -- PHP 5.3+ -- PHP [Zip](http://php.net/manual/en/book.zip.php) extension -- PHP [XML Parser](http://www.php.net/manual/en/xml.installation.php) extension - -Optional PHP extensions: - -- [GD](http://php.net/manual/en/book.image.php) -- [XMLWriter](http://php.net/manual/en/book.xmlwriter.php) -- [XSL](http://php.net/manual/en/book.xsl.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" - } -} -``` - -### Manual install - -To install manually, [download PHPWord package from github](https://github.com/PHPOffice/PHPWord/archive/master.zip). Extract the package and put the contents to your machine. To use the library, include `src/PhpWord/Autoloader.php` in your script and invoke `Autoloader::register`. - -```php -require_once '/path/to/src/PhpWord/Autoloader.php'; -\PhpOffice\PhpWord\Autoloader::register(); -``` - -## Using samples - -After installation, you can browse and use the samples that we've provided, either by command line or using browser. If you can access your PHPWord library folder using browser, point your browser to the `samples` folder, e.g. `http://localhost/PhpWord/samples/`. - -# General usage - -## 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( - htmlspecialchars( - '"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( - htmlspecialchars( - '"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( - htmlspecialchars( - '"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( - htmlspecialchars('"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. */ -``` - -## 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 -\PhpOffice\PhpWord\Settings::setCompatibility(false); -``` - -### Zip class - -By default, PHPWord uses PHP [ZipArchive](http://php.net/manual/en/book.zip.php) to read or write ZIP compressed archive and the files inside them. If you can't have ZipArchive installed on your server, you can use pure PHP library alternative, [PCLZip](http://www.phpconcept.net/pclzip/), which included with PHPWord. - -```php -\PhpOffice\PhpWord\Settings::setZipClass(\PhpOffice\PhpWord\Settings::PCLZIP); -``` - -## Default font - -By default, every text appears in Arial 10 point. You can alter the default font by using the following two functions: - -```php -$phpWord->setDefaultFontName('Times New Roman'); -$phpWord->setDefaultFontSize(12); -``` - -## Document information - -You can set the document information such as title, creator, and company name. Use the following functions: - -```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 twips. - -```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)); -``` - -# 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. - -## Sections - -Every visible element in word is placed inside of a section. To create a section, use the following code: - -```php -$section = $phpWord->addSection($sectionStyle); -``` - -The `$sectionStyle` is an optional associative array that sets the section. Example: - -```php -$sectionStyle = array( - 'orientation' => 'landscape', - 'marginTop' => 600, - 'colsNum' => 2, -); -``` - - -### Page number - -You can change a section page number by using the `pageNumberingStart` style of the section. - -```php -// Method 1 -$section = $phpWord->addSection(array('pageNumberingStart' => 1)); - -// Method 2 -$section = $phpWord->addSection(); -$section->getStyle()->setPageNumberingStart(1); -``` - -### Multicolumn - -You can change a section layout to multicolumn (like in a newspaper) by using the `breakType` and `colsNum` style of the section. - -```php -// Method 1 -$section = $phpWord->addSection(array('breakType' => 'continuous', 'colsNum' => 2)); - -// Method 2 -$section = $phpWord->addSection(); -$section->getStyle()->setBreakType('continuous'); -$section->getStyle()->setColsNum(2); -``` - -### Line numbering - -You can apply line numbering to a section by using the `lineNumbering` style of the section. - -```php -// Method 1 -$section = $phpWord->addSection(array('lineNumbering' => array())); - -// Method 2 -$section = $phpWord->addSection(); -$section->getStyle()->setLineNumbering(array()); -``` - -Below are the properties of the line numbering style. - -- `start` Line numbering starting value -- `increment` Line number increments -- `distance` Distance between text and line numbering in twip -- `restart` Line numbering restart setting continuous|newPage|newSection - -## Headers - -Each section can have its own header reference. To create a header use the `addHeader` method: - -```php -$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. Additionally, only inside of the header reference you can add watermarks or background pictures. See "Watermarks" section. - -## Footers - -Each section can have its own footer reference. To create a footer, use the `addFooter` method: - -```php -$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: - -- Texts `addText` and `createTextrun` -- Text breaks -- Images -- Tables -- Preserve text - -See the "Elements" section for the detail of each elements. - -## Other containers - -Textruns, table cells, and footnotes are elements that can also act as containers. See the corresponding "Elements" section for the detail of each elements. - -# 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 | v | v | v | v | v | v | -| 2 | Text Run | v | v | v | v | - | - | -| 3 | Link | v | v | v | v | v | v | -| 4 | Title | v | ? | ? | ? | ? | ? | -| 5 | Preserve Text | ? | v | v | v* | - | - | -| 6 | Text Break | v | v | v | v | v | v | -| 7 | Page Break | v | - | - | - | - | - | -| 8 | List | v | v | v | v | - | - | -| 9 | Table | v | v | v | v | - | - | -| 10 | Image | v | v | v | v | v | v | -| 11 | Watermark | - | v | - | - | - | - | -| 12 | Object | v | v | v | v | v | v | -| 13 | TOC | v | - | - | - | - | - | -| 14 | Footnote | v | - | - | v** | v** | - | -| 15 | Endnote | v | - | - | v** | v** | - | -| 16 | CheckBox | v | v | v | v | - | - | -| 17 | TextBox | v | v | v | v | - | - | -| 18 | Field | v | v | v | v | v | v | -| 19 | Line | v | v | v | v | v | v | -| 20 | Shape | v | v | v | v | v | v | -| 21 | Chart | v | - | - | - | - | - | -| 22 | Form Fields | v | v | v | v | v | v | - -Legend: - -- `v` Available -- `v*` Available only when inside header/footer -- `v**` Available only when inside section -- `-` Not available -- `?` Should be available - -## Texts - -Text can be added by using `addText` and `addTextRun` method. `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 -$section->addText($text, [$fontStyle], [$paragraphStyle]); -$textrun = $section->addTextRun([$paragraphStyle]); -``` - -You can use the `$fontStyle` and `$paragraphStyle` variable to define text formatting. There are 2 options to style the inserted text elements, i.e. inline style by using array or defined style by adding style definition. - -Inline style examples: - -```php -$fontStyle = array('name' => 'Times New Roman', 'size' => 9); -$paragraphStyle = array('align' => 'both'); -$section->addText('I am simple paragraph', $fontStyle, $paragraphStyle); - -$textrun = $section->addTextRun(); -$textrun->addText('I am bold', array('bold' => true)); -$textrun->addText('I am italic', array('italic' => true)); -$textrun->addText('I am colored', array('color' => 'AACC00')); -``` - -Defined style examples: - -```php -$fontStyle = array('color' => '006699', 'size' => 18, 'bold' => true); -$phpWord->addFontStyle('fStyle', $fontStyle); -$text = $section->addText('Hello world!', 'fStyle'); - -$paragraphStyle = array('align' => 'center'); -$phpWord->addParagraphStyle('pStyle', $paragraphStyle); -$text = $section->addText('Hello world!', 'pStyle'); -``` - -### 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. - -```php -$phpWord->addTitleStyle($depth, [$fontStyle], [$paragraphStyle]); -$section->addTitle($text, [$depth]); -``` - -Its 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: - -```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 "Font style" section. -- `$paragraphStyle` See "Paragraph style" section. - -### Preserve texts - -The `addPreserveText` method is used to add a page number or page count to headers or footers. - -```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 paramaters are optional. - -```php -$section->addTextBreak([$breakCount], [$fontStyle], [$paragraphStyle]); -``` - -- `$breakCount` How many lines -- `$fontStyle` See "Font style" section. -- `$paragraphStyle` See "Paragraph style" section. - -### Page breaks - -There are two ways to insert a page breaks, using the `addPageBreak` method or using the `pageBreakBefore` style of paragraph. - -```php -$section->addPageBreak(); -``` - -## Lists - -To add a list item use the function `addListItem`. - -Basic usage: - -```php -$section->addListItem($text, [$depth], [$fontStyle], [$listStyle], [$paragraphStyle]); -``` - -Parameters: - -- `$text` Text that appears in the document. -- `$depth` Depth of list item. -- `$fontStyle` See "Font style" section. -- `$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 "Paragraph style" section. - -Advanced usage: - -You can also create your own numbering style by changing the `$listStyle` parameter with the name of your numbering style. - -```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'); -``` - -## Tables - -To add tables, rows, and cells, use the `addTable`, `addRow`, and `addCell` methods: - -```php -$table = $section->addTable([$tableStyle]); -$table->addRow([$height], [$rowStyle]); -$cell = $table->addCell($width, [$cellStyle]); -``` - -Table style can be defined with `addTableStyle`: - -```php -$tableStyle = array( - 'borderColor' => '006699', - 'borderSize' => 6, - 'cellMargin' => 50 -); -$firstRowStyle = array('bgColor' => '66BBFF'); -$phpWord->addTableStyle('myTable', $tableStyle, $firstRowStyle); -$table = $section->addTable('myTable'); -``` - -### Cell span - -You can span a cell on multiple columns by using `gridSpan` or multiple rows by using `vMerge`. - -```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. - -```php -$section->addImage($src, [$style]); -``` - -- source String path to a local image or URL of a remote image -- styles Array fo styles for the image. See below. - -Examples: - -```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'); -``` - -### 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. - -```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 `addObject` method. - -```php -$section->addObject($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"). - -```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 PHPWord\_Style\_TOC. -- `tabPos` The position of the tab where the page number appears in twips. -- `indent` The indent factor of the titles in twips. - -## 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`, `addObject` on footnotes and endnotes. - -On textrun: - -```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: - -```php -$section->addText('Lead text.'); -$footnote = $section->addFootnote(); -$footnote->addText('Footnote text.'); -``` - -The footnote reference number will be displayed with decimal number starting from 1. This number use `FooterReference` style which you can redefine by `addFontStyle` method. Default value for this style is `array('superScript' => true)`; - -## Checkboxes - -Checkbox elements can be added to sections or table cells by using `addCheckBox`. - -```php -$section->addCheckBox($name, $text, [$fontStyle], [$paragraphStyle]) -``` - -- `$name` Name of the check box. -- `$text` Text following the check box -- `$fontStyle` See "Font style" section. -- `$paragraphStyle` See "Paragraph style" section. - -## Textboxes - -To be completed. - -## Fields - -To be completed. - -## Lines - -To be completed. - -## Shapes - -To be completed. - -## Charts - -To be completed. - -## Form fields - -To be completed. - -# Styles - -## Section - -Below are the available styles for section: - -- `orientation` Page orientation, i.e. 'portrait' (default) or 'landscape' -- `marginTop` Page margin top in twips -- `marginLeft` Page margin left in twips -- `marginRight` Page margin right in twips -- `marginBottom` Page margin bottom in twips -- `borderTopSize` Border top size in twips -- `borderTopColor` Border top color -- `borderLeftSize` Border left size in twips -- `borderLeftColor` Border left color -- `borderRightSize` Border right size in twips -- `borderRightColor` Border right color -- `borderBottomSize` Border bottom size in twips -- `borderBottomColor` Border bottom color -- `headerHeight` Spacing to top of header -- `footerHeight` Spacing to bottom of footer -- `gutter` Page gutter spacing -- `colsNum` Number of columns -- `colsSpace` Spacing between columns -- `breakType` Section break type (nextPage, nextColumn, continuous, evenPage, oddPage) - -The following two styles are automatically set by the use of the `orientation` style. You can alter them but that's not recommended. - -- `pageSizeW` Page width in twips -- `pageSizeH` Page height in twips - -## Font - -Available font styles: - -- `name` Font name, e.g. *Arial* -- `size` Font size, e.g. *20*, *22*, -- `hint` Font content type, *default*, *eastAsia*, or *cs* -- `bold` Bold, *true* or *false* -- `italic` Italic, *true* or *false* -- `superScript` Superscript, *true* or *false* -- `subScript` Subscript, *true* or *false* -- `underline` Underline, *dash*, *dotted*, etc. -- `strikethrough` Strikethrough, *true* or *false* -- `doubleStrikethrough` Double strikethrough, *true* or *false* -- `color` Font color, e.g. *FF0000* -- `fgColor` Font highlight color, e.g. *yellow*, *green*, *blue* -- `bgColor` Font background color, e.g. *FF0000* -- `smallCaps` Small caps, *true* or *false* -- `allCaps` All caps, *true* or *false* - -## Paragraph - -Available paragraph styles: - -- `align` Paragraph alignment, *left*, *right* or *center* -- `spaceBefore` Space before paragraph -- `spaceAfter` Space after paragraph -- `indent` Indent by how much -- `hanging` Hanging by how much -- `basedOn` Parent style -- `next` Style for next paragraph -- `widowControl` Allow first/last line to display on a separate page, *true* or *false* -- `keepNext` Keep paragraph with next paragraph, *true* or *false* -- `keepLines` Keep all lines on one page, *true* or *false* -- `pageBreakBefore` Start paragraph on next page, *true* or *false* -- `lineHeight` text line height, e.g. *1.0*, *1.5*, ect... -- `tabs` Set of custom tab stops - -## Table - -Table styles: - -- `width` Table width in percent -- `bgColor` Background color, e.g. '9966CC' -- `border(Top|Right|Bottom|Left)Size` Border size in twips -- `border(Top|Right|Bottom|Left)Color` Border color, e.g. '9966CC' -- `cellMargin(Top|Right|Bottom|Left)` Cell margin in twips - -Row styles: - -- `tblHeader` Repeat table row on every new page, *true* or *false* -- `cantSplit` Table row cannot break across pages, *true* or *false* -- `exactHeight` Row height is exact or at least - -Cell styles: - -- `width` Cell width in twips -- `valign` Vertical alignment, *top*, *center*, *both*, *bottom* -- `textDirection` Direction of text -- `bgColor` Background color, e.g. '9966CC' -- `border(Top|Right|Bottom|Left)Size` Border size in twips -- `border(Top|Right|Bottom|Left)Color` Border color, e.g. '9966CC' -- `gridSpan` Number of columns spanned -- `vMerge` *restart* or *continue* - -## Image - -Available image styles: - -- `width` Width in pixels -- `height` Height in pixels -- `align` Image alignment, *left*, *right*, or *center* -- `marginTop` Top margin in inches, can be negative -- `marginLeft` Left margin in inches, can be negative -- `wrappingStyle` Wrapping style, *inline*, *square*, *tight*, *behind*, or *infront* - -## Numbering level - -- `start` Starting value -- `format` Numbering format bullet|decimal|upperRoman|lowerRoman|upperLetter|lowerLetter -- `restart` Restart numbering level symbol -- `suffix` Content between numbering symbol and paragraph text tab|space|nothing -- `text` Numbering level text e.g. %1 for nonbullet or bullet character -- `align` Numbering symbol align left|center|right|both -- `left` See paragraph style -- `hanging` See paragraph style -- `tabPos` See paragraph style -- `font` Font name -- `hint` See font style - -# Templates processing - -You can create a .docx document template with included search-patterns which can be replaced by any value you wish. Only single-line values can be replaced. - -To deal with a template file, use `new TemplateProcessor` statement. After TemplateProcessor instance creation the document template is copied into the temporary directory. Then you can use `TemplateProcessor::setValue` method to change the value of a search pattern. The search-pattern model is: `${search-pattern}`. - -Example: - -```php -$templateProcessor = new TemplateProcessor('Template.docx'); -$templateProcessor->setValue('Name', 'Somebody someone'); -$templateProcessor->setValue('Street', 'Coming-Undone-Street 32'); -``` - -It is not possible to directly add new OOXML elements to the template file being processed, but it is possible to transform main document part of the template using XSLT (see `TemplateProcessor::applyXslStyleSheet`). - -See `Sample_07_TemplateCloneRow.php` for example on how to create multirow from a single row in a template by using `TemplateProcessor::cloneRow`. - -See `Sample_23_TemplateBlock.php` for example on how to clone a block of text using `TemplateProcessor::cloneBlock` and delete a block of text using `TemplateProcessor::deleteBlock`. - -# 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. - -# Recipes - -## Create float left image - -Use absolute positioning relative to margin horizontally and to line vertically. - -```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. - -```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. - -```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: - -```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). - -```php -$phpWord->getCompatibility()->setOoxmlVersion(15); -``` - -# Frequently asked questions - -## Is this the same with PHPWord that I found in CodePlex? - -No. This one is much better with tons of new features that you can’t find in PHPWord 0.6.3. The development in CodePlex is halted and switched to GitHub to allow more participation from the crowd. The more the merrier, right? - -## I’ve been running PHPWord from CodePlex flawlessly, but I can’t use the latest PHPWord from GitHub. Why? - -PHPWord requires PHP 5.3+ since 0.8, while PHPWord 0.6.3 from CodePlex can run with PHP 5.2. There’s a lot of new features that we can get from PHP 5.3 and it’s been around since 2009! You should upgrade your PHP version to use PHPWord 0.8+. - -# References - -## ISO/IEC 29500, Third edition, 2012-09-01 - -- [Part 1: Fundamentals and Markup Language Reference](http://standards.iso.org/ittf/PubliclyAvailableStandards/c061750_ISO_IEC_29500-1_2012.zip) -- [Part 2: Open Packaging Conventions](http://standards.iso.org/ittf/PubliclyAvailableStandards/c061796_ISO_IEC_29500-2_2012.zip) -- [Part 3: Markup Compatibility and Extensibility](http://standards.iso.org/ittf/PubliclyAvailableStandards/c061797_ISO_IEC_29500-3_2012.zip) -- [Part 4: Transitional Migration Features](http://standards.iso.org/ittf/PubliclyAvailableStandards/c061798_ISO_IEC_29500-4_2012.zip) - -## Formal specifications - -- [Oasis OpenDocument Standard Version 1.2](http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os.html) -- [Rich Text Format (RTF) Specification, version 1.9.1](http://www.microsoft.com/en-us/download/details.aspx?id=10725) - -## Other resources - -- [DocumentFormat.OpenXml.Wordprocessing Namespace on MSDN](http://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing%28v=office.14%29.aspx) diff --git a/docs/styles.rst b/docs/styles.rst deleted file mode 100644 index f4c1e8ebd4..0000000000 --- a/docs/styles.rst +++ /dev/null @@ -1,134 +0,0 @@ -.. _styles: - -Styles -====== - -Section -------- - -Below are the available styles for section: - -- ``pageSizeW`` Page width in twips (the default is 11906/A4 size) -- ``pageSizeH`` Page height in twips (the default is 16838/A4 size) -- ``orientation`` Page orientation, i.e. 'portrait' (default) or - 'landscape' -- ``marginTop`` Page margin top in twips -- ``marginLeft`` Page margin left in twips -- ``marginRight`` Page margin right in twips -- ``marginBottom`` Page margin bottom in twips -- ``borderTopSize`` Border top size in twips -- ``borderTopColor`` Border top color -- ``borderLeftSize`` Border left size in twips -- ``borderLeftColor`` Border left color -- ``borderRightSize`` Border right size in twips -- ``borderRightColor`` Border right color -- ``borderBottomSize`` Border bottom size in twips -- ``borderBottomColor`` Border bottom color -- ``headerHeight`` Spacing to top of header -- ``footerHeight`` Spacing to bottom of footer -- ``gutter`` Page gutter spacing -- ``colsNum`` Number of columns -- ``colsSpace`` Spacing between columns -- ``breakType`` Section break type (nextPage, nextColumn, continuous, - evenPage, oddPage) - -Font ----- - -Available font styles: - -- ``name`` Font name, e.g. *Arial* -- ``size`` Font size, e.g. *20*, *22*, -- ``hint`` Font content type, *default*, *eastAsia*, or *cs* -- ``bold`` Bold, *true* or *false* -- ``italic`` Italic, *true* or *false* -- ``superScript`` Superscript, *true* or *false* -- ``subScript`` Subscript, *true* or *false* -- ``underline`` Underline, *dash*, *dotted*, etc. -- ``strikethrough`` Strikethrough, *true* or *false* -- ``doubleStrikethrough`` Double strikethrough, *true* or *false* -- ``color`` Font color, e.g. *FF0000* -- ``fgColor`` Font highlight color, e.g. *yellow*, *green*, *blue* -- ``bgColor`` Font background color, e.g. *FF0000* -- ``smallCaps`` Small caps, *true* or *false* -- ``allCaps`` All caps, *true* or *false* -- ``rtl`` Right to Left language, *true* or *false* - -Paragraph ---------- - -Available paragraph styles: - -- ``align`` Paragraph alignment, *left*, *right* or *center* -- ``spaceBefore`` Space before paragraph -- ``spaceAfter`` Space after paragraph -- ``indent`` Indent by how much -- ``hanging`` Hanging by how much -- ``basedOn`` Parent style -- ``next`` Style for next paragraph -- ``widowControl`` Allow first/last line to display on a separate page, - *true* or *false* -- ``keepNext`` Keep paragraph with next paragraph, *true* or *false* -- ``keepLines`` Keep all lines on one page, *true* or *false* -- ``pageBreakBefore`` Start paragraph on next page, *true* or *false* -- ``lineHeight`` text line height, e.g. *1.0*, *1.5*, ect... -- ``tabs`` Set of custom tab stops - -Table ------ - -Table styles: - -- ``width`` Table width in percent -- ``bgColor`` Background color, e.g. '9966CC' -- ``border(Top|Right|Bottom|Left)Size`` Border size in twips -- ``border(Top|Right|Bottom|Left)Color`` Border color, e.g. '9966CC' -- ``cellMargin(Top|Right|Bottom|Left)`` Cell margin in twips - -Row styles: - -- ``tblHeader`` Repeat table row on every new page, *true* or *false* -- ``cantSplit`` Table row cannot break across pages, *true* or *false* -- ``exactHeight`` Row height is exact or at least - -Cell styles: - -- ``width`` Cell width in twips -- ``valign`` Vertical alignment, *top*, *center*, *both*, *bottom* -- ``textDirection`` Direction of text -- ``bgColor`` Background color, e.g. '9966CC' -- ``border(Top|Right|Bottom|Left)Size`` Border size in twips -- ``border(Top|Right|Bottom|Left)Color`` Border color, e.g. '9966CC' -- ``gridSpan`` Number of columns spanned -- ``vMerge`` *restart* or *continue* - -Image ------ - -Available image styles: - -- ``width`` Width in pixels -- ``height`` Height in pixels -- ``align`` Image alignment, *left*, *right*, or *center* -- ``marginTop`` Top margin in inches, can be negative -- ``marginLeft`` Left margin in inches, can be negative -- ``wrappingStyle`` Wrapping style, *inline*, *square*, *tight*, - *behind*, or *infront* - -Numbering level ---------------- - -- ``start`` Starting value -- ``format`` Numbering format - bullet\|decimal\|upperRoman\|lowerRoman\|upperLetter\|lowerLetter -- ``restart`` Restart numbering level symbol -- ``suffix`` Content between numbering symbol and paragraph text - tab\|space\|nothing -- ``text`` Numbering level text e.g. %1 for nonbullet or bullet - character -- ``align`` Numbering symbol align left\|center\|right\|both -- ``left`` See paragraph style -- ``hanging`` See paragraph style -- ``tabPos`` See paragraph style -- ``font`` Font name -- ``hint`` See font style diff --git a/docs/templates-processing.rst b/docs/templates-processing.rst deleted file mode 100644 index 6a65ea0d57..0000000000 --- a/docs/templates-processing.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. _templates-processing: - -Templates processing -==================== - -You can create a .docx document template with included search-patterns which can be replaced by any value you wish. Only single-line values can be replaced. - -To deal with a template file, use ``new TemplateProcessor`` statement. After TemplateProcessor instance creation the document template is copied into the temporary directory. Then you can use ``TemplateProcessor::setValue`` method to change the value of a search pattern. The search-pattern model is: ``${search-pattern}``. - -Example: - -.. code-block:: php - - $templateProcessor = new TemplateProcessor('Template.docx'); - $templateProcessor->setValue('Name', 'Somebody someone'); - $templateProcessor->setValue('Street', 'Coming-Undone-Street 32'); - -It is not possible to directly add new OOXML elements to the template file being processed, but it is possible to transform main document part of the template using XSLT (see ``TemplateProcessor::applyXslStyleSheet``). - -See ``Sample_07_TemplateCloneRow.php`` for example on how to create -multirow from a single row in a template by using ``TemplateProcessor::cloneRow``. - -See ``Sample_23_TemplateBlock.php`` for example on how to clone a block -of text using ``TemplateProcessor::cloneBlock`` and delete a block of text using -``TemplateProcessor::deleteBlock``. diff --git a/docs/usage/containers.md b/docs/usage/containers.md new file mode 100644 index 0000000000..e85a700f48 --- /dev/null +++ b/docs/usage/containers.md @@ -0,0 +1,140 @@ +# 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. + +## Sections + +Every visible element in word is placed inside of a section. To create a section, use the following code: + +``` php +addSection($sectionStyle); +``` + +The ``$sectionStyle`` is an optional associative array that sets the section. Example: + +``` php + 'landscape', + 'marginTop' => 600, + 'colsNum' => 2, +); +``` + +### Page number + +You can change a section page number by using the ``pageNumberingStart`` +style of the section. + +``` php +addSection(array('pageNumberingStart' => 1)); + +// Method 2 +$section = $phpWord->addSection(); +$section->getStyle()->setPageNumberingStart(1); +``` + +### Multicolumn + +You can change a section layout to multicolumn (like in a newspaper) by +using the ``breakType`` and ``colsNum`` style of the section. + +``` php +addSection(array('breakType' => 'continuous', 'colsNum' => 2)); + +// Method 2 +$section = $phpWord->addSection(); +$section->getStyle()->setBreakType('continuous'); +$section->getStyle()->setColsNum(2); +``` + +### Line numbering + +You can apply line numbering to a section by using the ``lineNumbering`` +style of the section. + +``` php +addSection(array('lineNumbering' => array())); + +// Method 2 +$section = $phpWord->addSection(); +$section->getStyle()->setLineNumbering(array()); +``` + +Below are the properties of the line numbering style. + +- ``start`` Line numbering starting value +- ``increment`` Line number increments +- ``distance`` Distance between text and line numbering in *twip* +- ``restart`` Line numbering restart setting + continuous\|newPage\|newSection + +## Headers + +Each section can have its own header reference. To create a header use +the ``addHeader`` method: + +``` php +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. +Additionally, only inside of the header reference you can add watermarks +or background pictures. See "Watermarks" section. + +You can pass an optional parameter to specify where the header/footer should be applied, it can be + +- ``Footer::AUTO`` default, all pages except if overridden by first or even +- ``Footer::FIRST`` each first page of the section +- ``Footer::EVEN`` each even page of the section. Will only be applied if the evenAndOddHeaders is set to true in phpWord->settings + +To change the evenAndOddHeaders use the ``getSettings`` method to return the Settings object, and then call the ``setEvenAndOddHeaders`` method: + +``` php +getSettings()->setEvenAndOddHeaders(true); +``` + +## Footers + +Each section can have its own footer reference. To create a footer, use +the ``addFooter`` method: + +``` php +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: + +- Texts ``addText`` and ``createTextrun`` +- Text breaks +- Images +- Tables +- Preserve text + +See the "Elements" section for the detail of each elements. + +### Other containers + +Textruns, table cells, and footnotes are elements that can also act as +containers. See the corresponding "Elements" section for the detail of +each elements. 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/ruby.md b/docs/usage/elements/ruby.md new file mode 100644 index 0000000000..508b97cd84 --- /dev/null +++ b/docs/usage/elements/ruby.md @@ -0,0 +1,57 @@ +# Ruby + +Ruby (phonetic guide) text can be added by using the ``addRuby`` method. Ruby elements require a ``RubyProperties`` object, a ``TextRun`` for the base text, and a ``TextRun`` for the actual ruby (phonetic guide) text. + +Here is one example for a complete ruby element setup: + +``` php +addSection(); +$properties = new RubyProperties(); +$properties->setAlignment(RubyProperties::ALIGNMENT_RIGHT_VERTICAL); +$properties->setFontFaceSize(10); +$properties->setFontPointsAboveBaseText(4); +$properties->setFontSizeForBaseText(18); +$properties->setLanguageId('ja-JP'); + +$baseTextRun = new TextRun(null); +$baseTextRun->addText('私'); +$rubyTextRun = new TextRun(null); +$rubyTextRun->addText('わたし'); + +$section->addRuby($baseTextRun, $rubyTextRun, $properties); +``` + +- ``$baseTextRun``. ``TextRun`` to be used for the base text. +- ``$rubyTextRun``. ``TextRun`` to be used for the ruby text. +- ``$properties``. ``RubyProperties`` properties object for the ruby text. + +A title with a phonetic guide is a little more complex, but still possible. Make sure you add the appropraite title style to your document. + +```php +$phpWord = new PhpWord(); +$fontStyle = new Font(); +$fontStyle->setAllCaps(true); +$fontStyle->setBold(true); +$fontStyle->setSize(24); +$phpWord->addTitleStyle(1, ['name' => 'Arial', 'size' => 24, 'bold' => true, 'color' => '990000']); + +$section = $phpWord->addSection(); +$properties = new RubyProperties(); +$properties->setAlignment(RubyProperties::ALIGNMENT_RIGHT_VERTICAL); +$properties->setFontFaceSize(10); +$properties->setFontPointsAboveBaseText(4); +$properties->setFontSizeForBaseText(18); +$properties->setLanguageId('ja-JP'); + +$baseTextRun = new TextRun(null); +$baseTextRun->addText('私'); +$rubyTextRun = new TextRun(null); +$rubyTextRun->addText('わたし'); + +$textRun = new TextRun(); +$textRun->addRuby($baseTextRun, $rubyTextRun, $properties); +$section->addTitle($textRun, 1); +``` \ 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..19d6aff51f --- /dev/null +++ b/docs/usage/introduction.md @@ -0,0 +1,392 @@ +# 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->setDefaultFontColor('FF0000'); +$phpWord->setDefaultFontSize(12); +``` + +Or you can specify Asian Font + +``` php +setDefaultAsianFontName('標楷體'); +``` + +## 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); +``` 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..f51d5ba030 --- /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*, *firstLineChars* 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..97708a0294 --- /dev/null +++ b/docs/usage/writers.md @@ -0,0 +1,152 @@ +# 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'); +``` + +or you can edit settings in phpword.ini ( or phpword.ini.dist) file. + +``` ini +pdfRendererName = MPDF ;DomPDF, TCPDF, MPDF +pdfRendererPath = /path/to/your/renderer/folder +``` + +## 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 34a0805a8c..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..97c842112c --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,121 @@ +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' + - Ruby: 'usage/elements/ruby.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.5.0 (WIP)': 'changes/1.x/1.5.0.md' + - '1.4.0': '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/phpmd.xml.dist b/phpmd.xml.dist index 5eb348ecfe..aeb2774881 100644 --- a/phpmd.xml.dist +++ b/phpmd.xml.dist @@ -4,15 +4,24 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0%20http://pmd.sf.net/ruleset_xml_schema.xsd" xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd"> - + + + + + + + + + + - + - + @@ -21,6 +30,5 @@ - \ No newline at end of file diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000000..ea75a4bd91 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,1877 @@ +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: "#^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\\:\\:parseRuby\\(\\) 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: 3 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Parameter \\#2 \\$element of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseNode\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\|PhpOffice\\\\PhpWord\\\\Element\\\\Row\\|PhpOffice\\\\PhpWord\\\\Element\\\\Table given\\.$#" + count: 1 + 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: "#^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\\\\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: "#^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: "#^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: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/HTML/Element/AbstractElement.php + + - + message: "#^Variable \\$row in PHPDoc tag @var does not match assigned variable \\$rowStyle\\.$#" + count: 1 + path: src/PhpWord/Writer/HTML/Element/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Field\\:\\:writeDefault\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Field.php + + - + message: "#^Variable \\$row in PHPDoc tag @var does not match any variable in the foreach loop\\: \\$cell$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$text with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$xmlWriter with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Text\\:\\:writeChangeInsertion\\(\\) has parameter \\$start with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Text.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getParagraphStyle\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/TextRun.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setColumnWidths\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Parameter \\#1 \\$container of method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:collectTrackedChanges\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:\\$imageParagraphStyles has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Writer/ODText/Part/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Styles\\:\\:cvttwiptostr\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Styles.php + + - + message: "#^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: "#^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\\\\AbstractWebServerEmbedded\\:\\:getBaseUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteBmpImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteGifImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:\\$httpServer has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Parameter \\#1 \\$width of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects int\\|null, string given\\.$#" + count: 2 + path: tests/PhpWordTests/Element/CellTest.php + + - + message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null, int given\\.$#" + count: 2 + path: tests/PhpWordTests/Element/CellTest.php + + - + message: "#^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 access property \\$length on DOMNodeList\\\\|false\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/HTML/Element/RubyTest.php + + - + message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" + count: 11 + path: tests/PhpWordTests/Writer/HTML/ElementTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 3 + path: tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\ODText\\\\Style\\\\FontTest\\:\\:providerAllNamedColors\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getOrientation\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getPaperSize\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getTempDir\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setOrientation\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setPaperSize\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setTempDir\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 3 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/MPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/MPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDFTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/ElementTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/ElementTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/StyleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/StyleTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\Writer\\\\Word2007\\\\Element\\\\ChartTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/StyleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getElement\\(\\) should return DOMElement\\|null but returns DOMNode\\|null\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getNodeList\\(\\) should return DOMNodeList\\ but returns DOMNodeList\\\\|false\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$path \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$xpath \\(DOMXPath\\) does not accept null\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + # https://github.com/phpstan/phpstan/issues/8770 + - + message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:|]+$#" + count: 1 + path: src/PhpWord/Writer/HTML.php + + # https://github.com/phpstan/phpstan/issues/8770 + - + message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" + count: 2 + path: tests/PhpWordTests/Element/AbstractElementTest.php + + # https://github.com/phpstan/phpstan/issues/8770 + - + message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" + count: 4 + path: tests/PhpWordTests/Style/AbstractStyleTest.php + + # https://github.com/phpstan/phpstan/issues/8770 + - + message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\EPub3\\\\Style\\\\AbstractStyle is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" + count: 2 + path: tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php + + # https://github.com/phpstan/phpstan/issues/8770 + - + message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" + count: 2 + path: tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php + + # https://github.com/phpstan/phpstan/issues/8770 + - + message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" + count: 2 + path: tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000000..cf26b6956b --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,45 @@ +includes: + - phpstan-baseline.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon +parameters: + level: 7 + paths: + - src/ + - tests/ + excludePaths: + - */pclzip.lib.php + - src/PhpWord/Shared/OLERead.php + - src/PhpWord/Reader/MsDoc.php + - src/PhpWord/Writer/PDF/MPDF.php + bootstrapFiles: + - tests/bootstrap.php + ## <=PHP7.4 + reportUnmatchedIgnoredErrors: false + treatPhpDocTypesAsCertain: false + ignoreErrors: + - + identifier: missingType.iterableValue + + ## <=PHP7.4 + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' + path: src/PhpWord/Element/AbstractContainer.php + - + message: '#Parameter \#1 \$function of function call_user_func expects callable\(\): mixed, string given.#' + path: src/PhpWord/Element/Image.php + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' + path: src/PhpWord/IOFactory.php + - + message: '#Parameter \#1 \$function of function forward_static_call_array expects callable\(\): mixed, array{.+, string} given.#' + path: src/PhpWord/PhpWord.php + - + message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{\$this\(PhpOffice\\PhpWord\\Shared\\ZipArchive\)\|PclZip\|ZipArchive, mixed} given.#' + path: src/PhpWord/Shared/ZipArchive.php + - + message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' + path: src/PhpWord/Writer/PDF.php + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' + path: tests/PhpWordTests/Style/AbstractStyleTest.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 015dd2edd1..6f1f5445ab 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,28 +1,24 @@ - - - - ./tests/PhpWord - - - - - ./src - - ./src/PhpWord/Shared/PCLZip - - - - - - - - \ No newline at end of file + + + + + ./src + + + ./src/PhpWord/Shared/PCLZip + + + + + + + + + + + + ./tests/PhpWordTests + + + + diff --git a/phpword.ini.dist b/phpword.ini.dist index f8eafb6a78..21d3b50609 100644 --- a/phpword.ini.dist +++ b/phpword.ini.dist @@ -3,13 +3,19 @@ [General] -compatibility = true -zipClass = ZipArchive -pdfRendererName = DomPDF -pdfRendererPath = -; tempDir = "C:\PhpWordTemp" +compatibility = true +zipClass = ZipArchive +pdfRendererName = DomPDF +pdfRendererPath = +; tempDir = "C:\PhpWordTemp" +outputEscapingEnabled = false [Font] defaultFontName = Arial defaultFontSize = 10 +defaultFontColor = 000000 + +[Paper] + +defaultPaper = "A4" diff --git a/samples/Sample_01_SimpleText.php b/samples/Sample_01_SimpleText.php index f7aaece30a..07e0d05aa5 100644 --- a/samples/Sample_01_SimpleText.php +++ b/samples/Sample_01_SimpleText.php @@ -1,27 +1,43 @@ addFontStyle('rStyle', array('bold' => true, 'italic' => true, 'size' => 16, 'allCaps' => true, 'doubleStrikethrough' => true)); -$phpWord->addParagraphStyle('pStyle', array('align' => 'center', 'spaceAfter' => 100)); -$phpWord->addTitleStyle(1, array('bold' => true), array('spaceAfter' => 240)); + +$languageEnGb = new PhpOffice\PhpWord\Style\Language(PhpOffice\PhpWord\Style\Language::EN_GB); + +$phpWord = new PhpOffice\PhpWord\PhpWord(); +$phpWord->getSettings()->setThemeFontLang($languageEnGb); + +$fontStyleName = 'rStyle'; +$phpWord->addFontStyle($fontStyleName, ['bold' => true, 'italic' => true, 'size' => 16, 'allCaps' => true, 'doubleStrikethrough' => true]); + +$paragraphStyleName = 'pStyle'; +$phpWord->addParagraphStyle($paragraphStyleName, ['alignment' => PhpOffice\PhpWord\SimpleType\Jc::CENTER, 'spaceAfter' => 100]); + +$phpWord->addTitleStyle(1, ['bold' => true], ['spaceAfter' => 240]); // New portrait section $section = $phpWord->addSection(); // Simple text -$section->addTitle(htmlspecialchars('Welcome to PhpWord'), 1); -$section->addText(htmlspecialchars('Hello World!')); +$section->addTitle('Welcome to PhpWord', 1); +$section->addText('Hello World!'); + +// $pStyle = new Font(); +// $pStyle->setLang() +$section->addText('Ce texte-ci est en français.', ['lang' => PhpOffice\PhpWord\Style\Language::FR_BE]); // Two text break $section->addTextBreak(2); -// Defined style -$section->addText(htmlspecialchars('I am styled by a font style definition.'), 'rStyle'); -$section->addText(htmlspecialchars('I am styled by a paragraph style definition.'), null, 'pStyle'); -$section->addText(htmlspecialchars('I am styled by both font and paragraph style.'), 'rStyle', 'pStyle'); +// Define styles +$section->addText('I am styled by a font style definition.', $fontStyleName); +$section->addText('I am styled by a paragraph style definition.', null, $paragraphStyleName); +$section->addText('I am styled by both font and paragraph style.', $fontStyleName, $paragraphStyleName); $section->addTextBreak(); @@ -30,43 +46,43 @@ $fontStyle['size'] = 20; $textrun = $section->addTextRun(); -$textrun->addText(htmlspecialchars('I am inline styled '), $fontStyle); -$textrun->addText(htmlspecialchars('with ')); -$textrun->addText(htmlspecialchars('color'), array('color' => '996699')); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('bold'), array('bold' => true)); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('italic'), array('italic' => true)); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('underline'), array('underline' => 'dash')); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('strikethrough'), array('strikethrough' => true)); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('doubleStrikethrough'), array('doubleStrikethrough' => true)); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('superScript'), array('superScript' => true)); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('subScript'), array('subScript' => true)); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('smallCaps'), array('smallCaps' => true)); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('allCaps'), array('allCaps' => true)); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('fgColor'), array('fgColor' => 'yellow')); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('scale'), array('scale' => 200)); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('spacing'), array('spacing' => 120)); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addText(htmlspecialchars('kerning'), array('kerning' => 10)); -$textrun->addText(htmlspecialchars('. ')); +$textrun->addText('I am inline styled ', $fontStyle); +$textrun->addText('with '); +$textrun->addText('color', ['color' => '996699']); +$textrun->addText(', '); +$textrun->addText('bold', ['bold' => true]); +$textrun->addText(', '); +$textrun->addText('italic', ['italic' => true]); +$textrun->addText(', '); +$textrun->addText('underline', ['underline' => 'dash']); +$textrun->addText(', '); +$textrun->addText('strikethrough', ['strikethrough' => true]); +$textrun->addText(', '); +$textrun->addText('doubleStrikethrough', ['doubleStrikethrough' => true]); +$textrun->addText(', '); +$textrun->addText('superScript', ['superScript' => true]); +$textrun->addText(', '); +$textrun->addText('subScript', ['subScript' => true]); +$textrun->addText(', '); +$textrun->addText('smallCaps', ['smallCaps' => true]); +$textrun->addText(', '); +$textrun->addText('allCaps', ['allCaps' => true]); +$textrun->addText(', '); +$textrun->addText('fgColor', ['fgColor' => 'yellow']); +$textrun->addText(', '); +$textrun->addText('scale', ['scale' => 200]); +$textrun->addText(', '); +$textrun->addText('spacing', ['spacing' => 120]); +$textrun->addText(', '); +$textrun->addText('kerning', ['kerning' => 10]); +$textrun->addText('. '); // Link -$section->addLink('http://www.google.com/', htmlspecialchars('Google')); +$section->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord on GitHub'); $section->addTextBreak(); // Image -$section->addImage('resources/_earth.jpg', array('width'=>18, 'height'=>18)); +$section->addImage(__DIR__ . '/resources/_earth.jpg', ['width' => 18, 'height' => 18]); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_02_TabStops.php b/samples/Sample_02_TabStops.php index 46d91cec34..ad99fe83f5 100644 --- a/samples/Sample_02_TabStops.php +++ b/samples/Sample_02_TabStops.php @@ -1,37 +1,37 @@ addParagraphStyle( - 'multipleTab', - array( - 'tabs' => array( - new \PhpOffice\PhpWord\Style\Tab('left', 1550), - new \PhpOffice\PhpWord\Style\Tab('center', 3200), - new \PhpOffice\PhpWord\Style\Tab('right', 5300), - ) - ) -); +// Define styles +$multipleTabsStyleName = 'multipleTab'; $phpWord->addParagraphStyle( - 'rightTab', - array('tabs' => array(new \PhpOffice\PhpWord\Style\Tab('right', 9090))) -); -$phpWord->addParagraphStyle( - 'centerTab', - array('tabs' => array(new \PhpOffice\PhpWord\Style\Tab('center', 4680))) + $multipleTabsStyleName, + [ + 'tabs' => [ + new PhpOffice\PhpWord\Style\Tab('left', 1550), + new PhpOffice\PhpWord\Style\Tab('center', 3200), + new PhpOffice\PhpWord\Style\Tab('right', 5300), + ], + ] ); +$rightTabStyleName = 'rightTab'; +$phpWord->addParagraphStyle($rightTabStyleName, ['tabs' => [new PhpOffice\PhpWord\Style\Tab('right', 9090)]]); + +$leftTabStyleName = 'centerTab'; +$phpWord->addParagraphStyle($leftTabStyleName, ['tabs' => [new PhpOffice\PhpWord\Style\Tab('center', 4680)]]); + // New portrait section $section = $phpWord->addSection(); // Add listitem elements -$section->addText(htmlspecialchars("Multiple Tabs:\tOne\tTwo\tThree"), null, 'multipleTab'); -$section->addText(htmlspecialchars("Left Aligned\tRight Aligned"), null, 'rightTab'); -$section->addText(htmlspecialchars("\tCenter Aligned"), null, 'centerTab'); +$section->addText("Multiple Tabs:\tOne\tTwo\tThree", null, $multipleTabsStyleName); +$section->addText("Left Aligned\tRight Aligned", null, $rightTabStyleName); +$section->addText("\tCenter Aligned", null, $leftTabStyleName); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_03_Sections.php b/samples/Sample_03_Sections.php index a95b15d644..b01726d81c 100644 --- a/samples/Sample_03_Sections.php +++ b/samples/Sample_03_Sections.php @@ -1,44 +1,49 @@ addSection(array('borderColor' => '00FF00', 'borderSize' => 12)); -$section->addText(htmlspecialchars('I am placed on a default section.')); +$section = $phpWord->addSection(['borderColor' => '00FF00', 'borderSize' => 12]); +$section->addText('I am placed on a default section.'); // New landscape section -$section = $phpWord->addSection(array('orientation' => 'landscape')); -$section->addText( - htmlspecialchars( - 'I am placed on a landscape section. Every page starting from this section will be landscape style.' - ) -); +$section = $phpWord->addSection(['orientation' => 'landscape']); +$section->addText('I am placed on a landscape section. Every page starting from this section will be landscape style.'); $section->addPageBreak(); $section->addPageBreak(); // New portrait section $section = $phpWord->addSection( - array('paperSize' => 'Folio', 'marginLeft' => 600, 'marginRight' => 600, 'marginTop' => 600, 'marginBottom' => 600) + ['paperSize' => 'Folio', 'marginLeft' => 600, 'marginRight' => 600, 'marginTop' => 600, 'marginBottom' => 600] +); +$section->addText('This section uses other margins with folio papersize.'); + +// The text of this section is vertically centered +$section = $phpWord->addSection( + ['vAlign' => VerticalJc::CENTER] ); -$section->addText(htmlspecialchars('This section uses other margins with folio papersize.')); +$section->addText('This section is vertically centered.'); // New portrait section with Header & Footer $section = $phpWord->addSection( - array( - 'marginLeft' => 200, - 'marginRight' => 200, - 'marginTop' => 200, + [ + 'marginLeft' => 200, + 'marginRight' => 200, + 'marginTop' => 200, 'marginBottom' => 200, 'headerHeight' => 50, 'footerHeight' => 50, - ) + ] ); -$section->addText(htmlspecialchars('This section and we play with header/footer height.')); -$section->addHeader()->addText(htmlspecialchars('Header')); -$section->addFooter()->addText(htmlspecialchars('Footer')); +$section->addText('This section and we play with header/footer height.'); +$section->addHeader()->addText('Header'); +$section->addFooter()->addText('Footer'); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_04_Textrun.php b/samples/Sample_04_Textrun.php index 7ebf6e3365..6d50e285ec 100644 --- a/samples/Sample_04_Textrun.php +++ b/samples/Sample_04_Textrun.php @@ -1,43 +1,47 @@ addParagraphStyle($paragraphStyleName, ['spacing' => 100]); + +$boldFontStyleName = 'BoldText'; +$phpWord->addFontStyle($boldFontStyleName, ['bold' => true]); -// Ads styles -$phpWord->addParagraphStyle('pStyle', array('spacing' => 100)); -$phpWord->addFontStyle('BoldText', array('bold' => true)); -$phpWord->addFontStyle('ColoredText', array('color' => 'FF8080', 'bgColor' => 'FFFFCC')); -$phpWord->addLinkStyle( - 'NLink', - array('color' => '0000FF', 'underline' => \PhpOffice\PhpWord\Style\Font::UNDERLINE_SINGLE) -); +$coloredFontStyleName = 'ColoredText'; +$phpWord->addFontStyle($coloredFontStyleName, ['color' => 'FF8080', 'bgColor' => 'FFFFCC']); + +$linkFontStyleName = 'NLink'; +$phpWord->addLinkStyle($linkFontStyleName, ['color' => '0000FF', 'underline' => PhpOffice\PhpWord\Style\Font::UNDERLINE_SINGLE]); // New portrait section $section = $phpWord->addSection(); // Add text run -$textrun = $section->addTextRun('pStyle'); - -$textrun->addText(htmlspecialchars('Each textrun can contain native text, link elements or an image.')); -$textrun->addText(htmlspecialchars(' No break is placed after adding an element.'), 'BoldText'); -$textrun->addText(htmlspecialchars(' Both ')); -$textrun->addText(htmlspecialchars('superscript'), array('superScript' => true)); -$textrun->addText(htmlspecialchars(' and ')); -$textrun->addText(htmlspecialchars('subscript'), array('subScript' => true)); -$textrun->addText(htmlspecialchars(' are also available.')); -$textrun->addText( - htmlspecialchars(' All elements are placed inside a paragraph with the optionally given p-Style.'), - 'ColoredText' -); -$textrun->addText(htmlspecialchars(' Sample Link: ')); -$textrun->addLink('http://www.google.com/', null, 'NLink'); -$textrun->addText(htmlspecialchars(' Sample Image: ')); -$textrun->addImage('resources/_earth.jpg', array('width' => 18, 'height' => 18)); -$textrun->addText(htmlspecialchars(' Sample Object: ')); -$textrun->addObject('resources/_sheet.xls'); -$textrun->addText(htmlspecialchars(' Here is some more text. ')); +$textrun = $section->addTextRun($paragraphStyleName); +$textrun->addText('Each textrun can contain native text, link elements or an image.'); +$textrun->addText(' No break is placed after adding an element.', $boldFontStyleName); +$textrun->addText(' Both '); +$textrun->addText('superscript', ['superScript' => true]); +$textrun->addText(' and '); +$textrun->addText('subscript', ['subScript' => true]); +$textrun->addText(' are also available.'); +$textrun->addText(' All elements are placed inside a paragraph with the optionally given paragraph style.', $coloredFontStyleName); +$textrun->addText(' Sample Link: '); +$textrun->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord on GitHub', $linkFontStyleName); +$textrun->addText(' Sample Image: '); +$textrun->addImage(__DIR__ . '/resources/_earth.jpg', ['width' => 18, 'height' => 18]); +$textrun->addText(' Sample Object: '); +$textrun->addObject(__DIR__ . '/resources/_sheet.xls'); +$textrun->addText(' Here is some more text. '); + +$textrun = $section->addTextRun(); +$textrun->addText('This text is not visible.', ['hidden' => true]); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_05_Multicolumn.php b/samples/Sample_05_Multicolumn.php index b93ab344c1..c4d1dd7280 100644 --- a/samples/Sample_05_Multicolumn.php +++ b/samples/Sample_05_Multicolumn.php @@ -1,9 +1,10 @@ addSection(); -$section->addText(htmlspecialchars("Normal paragraph. {$filler}")); +$section->addText("Normal paragraph. {$filler}"); // Two columns $section = $phpWord->addSection( - array( - 'colsNum' => 2, + [ + 'colsNum' => 2, 'colsSpace' => 1440, 'breakType' => 'continuous', - ) + ] ); -$section->addText(htmlspecialchars("Two columns, one inch (1440 twips) spacing. {$filler}")); +$section->addText("Two columns, one inch (1440 twips) spacing. {$filler}"); // Normal -$section = $phpWord->addSection(array('breakType' => 'continuous')); -$section->addText(htmlspecialchars("Normal paragraph again. {$filler}")); +$section = $phpWord->addSection(['breakType' => 'continuous']); +$section->addText("Normal paragraph again. {$filler}"); // Three columns $section = $phpWord->addSection( - array( - 'colsNum' => 3, + [ + 'colsNum' => 3, 'colsSpace' => 720, 'breakType' => 'continuous', - ) + ] ); -$section->addText(htmlspecialchars("Three columns, half inch (720 twips) spacing. {$filler}")); +$section->addText("Three columns, half inch (720 twips) spacing. {$filler}"); // Normal -$section = $phpWord->addSection(array('breakType' => 'continuous')); -$section->addText(htmlspecialchars('Normal paragraph again.')); +$section = $phpWord->addSection(['breakType' => 'continuous']); +$section->addText("Normal paragraph again. {$filler}"); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_06_Footnote.php b/samples/Sample_06_Footnote.php index c7f3d8c0bc..035916ee09 100644 --- a/samples/Sample_06_Footnote.php +++ b/samples/Sample_06_Footnote.php @@ -1,49 +1,59 @@ addParagraphStyle($paragraphStyleName, ['spacing' => 100]); + +$boldFontStyleName = 'BoldText'; +$phpWord->addFontStyle($boldFontStyleName, ['bold' => true]); + +$coloredFontStyleName = 'ColoredText'; +$phpWord->addFontStyle($coloredFontStyleName, ['color' => 'FF8080', 'bgColor' => 'FFFFCC']); + +$linkFontStyleName = 'NLink'; +$phpWord->addLinkStyle($linkFontStyleName, ['color' => '0000FF', 'underline' => PhpOffice\PhpWord\Style\Font::UNDERLINE_SINGLE]); // New portrait section $section = $phpWord->addSection(); -// Add style definitions -$phpWord->addParagraphStyle('pStyle', array('spacing' => 100)); -$phpWord->addFontStyle('BoldText', array('bold' => true)); -$phpWord->addFontStyle('ColoredText', array('color' => 'FF8080')); -$phpWord->addLinkStyle( - 'NLink', - array('color' => '0000FF', 'underline' => \PhpOffice\PhpWord\Style\Font::UNDERLINE_SINGLE) -); - // Add text elements -$textrun = $section->addTextRun('pStyle'); -$textrun->addText(htmlspecialchars('This is some lead text in a paragraph with a following footnote. '), 'pStyle'); +$textrun = $section->addTextRun($paragraphStyleName); +$textrun->addText('This is some lead text in a paragraph with a following footnote. ', $paragraphStyleName); $footnote = $textrun->addFootnote(); -$footnote->addText(htmlspecialchars('Just like a textrun, a footnote can contain native texts. ')); -$footnote->addText(htmlspecialchars('No break is placed after adding an element. '), 'BoldText'); -$footnote->addText(htmlspecialchars('All elements are placed inside a paragraph. '), 'ColoredText'); +$footnote->addText('Just like a textrun, a footnote can contain native texts. '); +$footnote->addText('No break is placed after adding an element. ', $boldFontStyleName); +$footnote->addText('All elements are placed inside a paragraph. ', $coloredFontStyleName); $footnote->addTextBreak(); -$footnote->addText(htmlspecialchars('But you can insert a manual text break like above, ')); -$footnote->addText(htmlspecialchars('links like ')); -$footnote->addLink('http://www.google.com/', null, 'NLink'); -$footnote->addText(htmlspecialchars(', image like ')); -$footnote->addImage('resources/_earth.jpg', array('width' => 18, 'height' => 18)); -$footnote->addText(htmlspecialchars(', or object like ')); -$footnote->addObject('resources/_sheet.xls'); -$footnote->addText(htmlspecialchars('But you can only put footnote in section, not in header or footer.')); +$footnote->addText('But you can insert a manual text break like above, '); +$footnote->addText('links like '); +$footnote->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord on GitHub', $linkFontStyleName); +$footnote->addText(', image like '); +$footnote->addImage(__DIR__ . '/resources/_earth.jpg', ['width' => 18, 'height' => 18]); +$footnote->addText(', or object like '); +$footnote->addObject(__DIR__ . '/resources/_sheet.xls'); +$footnote->addText('But you can only put footnote in section, not in header or footer.'); $section->addText( - htmlspecialchars( - 'You can also create the footnote directly from the section making it wrap in a paragraph ' - . 'like the footnote below this paragraph. But is is best used from within a textrun.' - ) + 'You can also create the footnote directly from the section making it wrap in a paragraph ' + . 'like the footnote below this paragraph. But is best used from within a textrun.' ); $footnote = $section->addFootnote(); -$footnote->addText(htmlspecialchars('The reference for this is wrapped in its own line')); +$footnote->addText('The reference for this is wrapped in its own line'); + +$footnoteProperties = new FootnoteProperties(); +$footnoteProperties->setNumFmt(NumberFormat::DECIMAL_ENCLOSED_CIRCLE); +$section->setFootnoteProperties($footnoteProperties); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_07_TemplateCloneRow.php b/samples/Sample_07_TemplateCloneRow.php index 60edacb5a7..0f1d165f19 100644 --- a/samples/Sample_07_TemplateCloneRow.php +++ b/samples/Sample_07_TemplateCloneRow.php @@ -1,62 +1,87 @@ -setValue('weekday', htmlspecialchars(date('l'))); // On section/content -$templateProcessor->setValue('time', htmlspecialchars(date('H:i'))); // On footer -$templateProcessor->setValue('serverName', htmlspecialchars(realpath(__DIR__))); // On header - -// Simple table -$templateProcessor->cloneRow('rowValue', 10); - -$templateProcessor->setValue('rowValue#1', htmlspecialchars('Sun')); -$templateProcessor->setValue('rowValue#2', htmlspecialchars('Mercury')); -$templateProcessor->setValue('rowValue#3', htmlspecialchars('Venus')); -$templateProcessor->setValue('rowValue#4', htmlspecialchars('Earth')); -$templateProcessor->setValue('rowValue#5', htmlspecialchars('Mars')); -$templateProcessor->setValue('rowValue#6', htmlspecialchars('Jupiter')); -$templateProcessor->setValue('rowValue#7', htmlspecialchars('Saturn')); -$templateProcessor->setValue('rowValue#8', htmlspecialchars('Uranus')); -$templateProcessor->setValue('rowValue#9', htmlspecialchars('Neptun')); -$templateProcessor->setValue('rowValue#10', htmlspecialchars('Pluto')); - -$templateProcessor->setValue('rowNumber#1', htmlspecialchars('1')); -$templateProcessor->setValue('rowNumber#2', htmlspecialchars('2')); -$templateProcessor->setValue('rowNumber#3', htmlspecialchars('3')); -$templateProcessor->setValue('rowNumber#4', htmlspecialchars('4')); -$templateProcessor->setValue('rowNumber#5', htmlspecialchars('5')); -$templateProcessor->setValue('rowNumber#6', htmlspecialchars('6')); -$templateProcessor->setValue('rowNumber#7', htmlspecialchars('7')); -$templateProcessor->setValue('rowNumber#8', htmlspecialchars('8')); -$templateProcessor->setValue('rowNumber#9', htmlspecialchars('9')); -$templateProcessor->setValue('rowNumber#10', htmlspecialchars('10')); - -// Table with a spanned cell -$templateProcessor->cloneRow('userId', 3); - -$templateProcessor->setValue('userId#1', htmlspecialchars('1')); -$templateProcessor->setValue('userFirstName#1', htmlspecialchars('James')); -$templateProcessor->setValue('userName#1', htmlspecialchars('Taylor')); -$templateProcessor->setValue('userPhone#1', htmlspecialchars('+1 428 889 773')); - -$templateProcessor->setValue('userId#2', htmlspecialchars('2')); -$templateProcessor->setValue('userFirstName#2', htmlspecialchars('Robert')); -$templateProcessor->setValue('userName#2', htmlspecialchars('Bell')); -$templateProcessor->setValue('userPhone#2', htmlspecialchars('+1 428 889 774')); - -$templateProcessor->setValue('userId#3', htmlspecialchars('3')); -$templateProcessor->setValue('userFirstName#3', htmlspecialchars('Michael')); -$templateProcessor->setValue('userName#3', htmlspecialchars('Ray')); -$templateProcessor->setValue('userPhone#3', htmlspecialchars('+1 428 889 775')); - -echo date('H:i:s'), ' Saving the result document...', EOL; -$templateProcessor->saveAs('results/Sample_07_TemplateCloneRow.docx'); - -echo getEndingNotes(array('Word2007' => 'docx')); -if (!CLI) { - include_once 'Sample_Footer.php'; -} +setValue('weekday', date('l')); // On section/content +$templateProcessor->setValue('time', date('H:i')); // On footer +$templateProcessor->setValue('serverName', realpath(__DIR__)); // On header + +// Simple table +$templateProcessor->cloneRow('rowValue', 10); + +$templateProcessor->setValue('rowValue#1', 'Sun'); +$templateProcessor->setValue('rowValue#2', 'Mercury'); +$templateProcessor->setValue('rowValue#3', 'Venus'); +$templateProcessor->setValue('rowValue#4', 'Earth'); +$templateProcessor->setValue('rowValue#5', 'Mars'); +$templateProcessor->setValue('rowValue#6', 'Jupiter'); +$templateProcessor->setValue('rowValue#7', 'Saturn'); +$templateProcessor->setValue('rowValue#8', 'Uranus'); +$templateProcessor->setValue('rowValue#9', 'Neptun'); +$templateProcessor->setValue('rowValue#10', 'Pluto'); + +$templateProcessor->setValue('rowNumber#1', '1'); +$templateProcessor->setValue('rowNumber#2', '2'); +$templateProcessor->setValue('rowNumber#3', '3'); +$templateProcessor->setValue('rowNumber#4', '4'); +$templateProcessor->setValue('rowNumber#5', '5'); +$templateProcessor->setValue('rowNumber#6', '6'); +$templateProcessor->setValue('rowNumber#7', '7'); +$templateProcessor->setValue('rowNumber#8', '8'); +$templateProcessor->setValue('rowNumber#9', '9'); +$templateProcessor->setValue('rowNumber#10', '10'); + +// Table with a spanned cell +$values = [ + [ + 'userId' => 1, + 'userFirstName' => 'James', + 'userName' => 'Taylor', + 'userPhone' => '+1 428 889 773', + ], + [ + 'userId' => 2, + 'userFirstName' => 'Robert', + 'userName' => 'Bell', + 'userPhone' => '+1 428 889 774', + ], + [ + 'userId' => 3, + 'userFirstName' => 'Michael', + 'userName' => 'Ray', + 'userPhone' => '+1 428 889 775', + ], +]; + +$templateProcessor->cloneRowAndSetValues('userId', $values); + +//this is equivalent to cloning and settings values with cloneRowAndSetValues +// $templateProcessor->cloneRow('userId', 3); + +// $templateProcessor->setValue('userId#1', '1'); +// $templateProcessor->setValue('userFirstName#1', 'James'); +// $templateProcessor->setValue('userName#1', 'Taylor'); +// $templateProcessor->setValue('userPhone#1', '+1 428 889 773'); + +// $templateProcessor->setValue('userId#2', '2'); +// $templateProcessor->setValue('userFirstName#2', 'Robert'); +// $templateProcessor->setValue('userName#2', 'Bell'); +// $templateProcessor->setValue('userPhone#2', '+1 428 889 774'); + +// $templateProcessor->setValue('userId#3', '3'); +// $templateProcessor->setValue('userFirstName#3', 'Michael'); +// $templateProcessor->setValue('userName#3', 'Ray'); +// $templateProcessor->setValue('userPhone#3', '+1 428 889 775'); + +echo date('H:i:s'), ' Saving the result document...', EOL; +$templateProcessor->saveAs(__DIR__ . '/results/Sample_07_TemplateCloneRow.docx'); + +echo getEndingNotes(['Word2007' => 'docx'], __DIR__ . '/results/Sample_07_TemplateCloneRow.docx'); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/Sample_08_ParagraphPagination.php b/samples/Sample_08_ParagraphPagination.php index dd364562d0..cac52e3baf 100644 --- a/samples/Sample_08_ParagraphPagination.php +++ b/samples/Sample_08_ParagraphPagination.php @@ -1,75 +1,66 @@ setDefaultParagraphStyle( - array( - 'align' => 'both', - 'spaceAfter' => \PhpOffice\PhpWord\Shared\Converter::pointToTwip(12), - 'spacing' => 120, - ) + [ + 'alignment' => PhpOffice\PhpWord\SimpleType\Jc::BOTH, + 'spaceAfter' => PhpOffice\PhpWord\Shared\Converter::pointToTwip(12), + 'spacing' => 120, + ] ); -// Sample +// New section $section = $phpWord->addSection(); $section->addText( - htmlspecialchars( - 'Below are the samples on how to control your paragraph ' - . 'pagination. See "Line and Page Break" tab on paragraph properties ' - . 'window to see the attribute set by these controls.' - ), - array('bold' => true), - array('space' => array('before' => 360, 'after' => 480)) + 'Below are the samples on how to control your paragraph ' + . 'pagination. See "Line and Page Break" tab on paragraph properties ' + . 'window to see the attribute set by these controls.', + ['bold' => true], + ['space' => ['before' => 360, 'after' => 480]] ); $section->addText( - htmlspecialchars( - 'Paragraph with widowControl = false (default: true). ' - . 'A "widow" is the last line of a paragraph printed by itself at the top ' - . 'of a page. An "orphan" is the first line of a paragraph printed by ' - . 'itself at the bottom of a page. Set this option to "false" if you want ' - . 'to disable this automatic control.' - ), + 'Paragraph with widowControl = false (default: true). ' + . 'A "widow" is the last line of a paragraph printed by itself at the top ' + . 'of a page. An "orphan" is the first line of a paragraph printed by ' + . 'itself at the bottom of a page. Set this option to "false" if you want ' + . 'to disable this automatic control.', null, - array('widowControl' => false, 'indentation' => array('left' => 240, 'right' => 120)) + ['widowControl' => false, 'indentation' => ['left' => 240, 'right' => 120]] ); $section->addText( - htmlspecialchars( - 'Paragraph with keepNext = true (default: false). ' - . '"Keep with next" is used to prevent Word from inserting automatic page ' - . 'breaks between paragraphs. Set this option to "true" if you do not want ' - . 'your paragraph to be separated with the next paragraph.' - ), + 'Paragraph with keepNext = true (default: false). ' + . '"Keep with next" is used to prevent Word from inserting automatic page ' + . 'breaks between paragraphs. Set this option to "true" if you do not want ' + . 'your paragraph to be separated with the next paragraph.', null, - array('keepNext' => true, 'indentation' => array('firstLine' => 240)) + ['keepNext' => true, 'indentation' => ['firstLine' => 240]] ); $section->addText( - htmlspecialchars( - 'Paragraph with keepLines = true (default: false). ' - . '"Keep lines together" will prevent Word from inserting an automatic page ' - . 'break within a paragraph. Set this option to "true" if you do not want ' - . 'all lines of your paragraph to be in the same page.' - ), + 'Paragraph with keepLines = true (default: false). ' + . '"Keep lines together" will prevent Word from inserting an automatic page ' + . 'break within a paragraph. Set this option to "true" if you do not want ' + . 'all lines of your paragraph to be in the same page.', null, - array('keepLines' => true, 'indentation' => array('left' => 240, 'hanging' => 240)) + ['keepLines' => true, 'indentation' => ['left' => 240, 'hanging' => 240]] ); -$section->addText(htmlspecialchars('Keep scrolling. More below.')); +$section->addText('Keep scrolling. More below.'); $section->addText( - htmlspecialchars( - 'Paragraph with pageBreakBefore = true (default: false). ' - . 'Different with all other control above, "page break before" separates ' - . 'your paragraph into the next page. This option is most useful for ' - . 'heading styles.' - ), + 'Paragraph with pageBreakBefore = true (default: false). ' + . 'Different with all other control above, "page break before" separates ' + . 'your paragraph into the next page. This option is most useful for ' + . 'heading styles.', null, - array('pageBreakBefore' => true) + ['pageBreakBefore' => true] ); // Save file diff --git a/samples/Sample_09_Tables.php b/samples/Sample_09_Tables.php index 5c305c8342..3a887468b0 100644 --- a/samples/Sample_09_Tables.php +++ b/samples/Sample_09_Tables.php @@ -1,99 +1,153 @@ addSection(); -$header = array('size' => 16, 'bold' => true); +$header = ['size' => 16, 'bold' => true]; // 1. Basic table $rows = 10; $cols = 5; -$section->addText(htmlspecialchars('Basic table'), $header); +$section->addText('Basic table', $header); $table = $section->addTable(); -for ($r = 1; $r <= 8; $r++) { +for ($r = 1; $r <= $rows; ++$r) { $table->addRow(); - for ($c = 1; $c <= 5; $c++) { - $table->addCell(1750)->addText(htmlspecialchars("Row {$r}, Cell {$c}")); + for ($c = 1; $c <= $cols; ++$c) { + $table->addCell(1750)->addText("Row {$r}, Cell {$c}"); } } // 2. Advanced table $section->addTextBreak(1); -$section->addText(htmlspecialchars('Fancy table'), $header); - -$styleTable = array('borderSize' => 6, 'borderColor' => '006699', 'cellMargin' => 80); -$styleFirstRow = array('borderBottomSize' => 18, 'borderBottomColor' => '0000FF', 'bgColor' => '66BBFF'); -$styleCell = array('valign' => 'center'); -$styleCellBTLR = array('valign' => 'center', 'textDirection' => \PhpOffice\PhpWord\Style\Cell::TEXT_DIR_BTLR); -$fontStyle = array('bold' => true, 'align' => 'center'); -$phpWord->addTableStyle('Fancy Table', $styleTable, $styleFirstRow); -$table = $section->addTable('Fancy Table'); +$section->addText('Fancy table', $header); + +$fancyTableStyleName = 'Fancy Table'; +$fancyTableStyle = ['borderSize' => 6, 'borderColor' => '006699', 'cellMargin' => 80, 'alignment' => PhpOffice\PhpWord\SimpleType\JcTable::CENTER, 'cellSpacing' => 50]; +$fancyTableFirstRowStyle = ['borderBottomSize' => 18, 'borderBottomColor' => '0000FF', 'bgColor' => '66BBFF']; +$fancyTableCellStyle = ['valign' => 'center']; +$fancyTableCellBtlrStyle = ['valign' => 'center', 'textDirection' => PhpOffice\PhpWord\Style\Cell::TEXT_DIR_BTLR]; +$fancyTableFontStyle = ['bold' => true]; +$phpWord->addTableStyle($fancyTableStyleName, $fancyTableStyle, $fancyTableFirstRowStyle); +$table = $section->addTable($fancyTableStyleName); $table->addRow(900); -$table->addCell(2000, $styleCell)->addText(htmlspecialchars('Row 1'), $fontStyle); -$table->addCell(2000, $styleCell)->addText(htmlspecialchars('Row 2'), $fontStyle); -$table->addCell(2000, $styleCell)->addText(htmlspecialchars('Row 3'), $fontStyle); -$table->addCell(2000, $styleCell)->addText(htmlspecialchars('Row 4'), $fontStyle); -$table->addCell(500, $styleCellBTLR)->addText(htmlspecialchars('Row 5'), $fontStyle); -for ($i = 1; $i <= 8; $i++) { +$table->addCell(2000, $fancyTableCellStyle)->addText('Row 1', $fancyTableFontStyle); +$table->addCell(2000, $fancyTableCellStyle)->addText('Row 2', $fancyTableFontStyle); +$table->addCell(2000, $fancyTableCellStyle)->addText('Row 3', $fancyTableFontStyle); +$table->addCell(2000, $fancyTableCellStyle)->addText('Row 4', $fancyTableFontStyle); +$table->addCell(500, $fancyTableCellBtlrStyle)->addText('Row 5', $fancyTableFontStyle); +for ($i = 1; $i <= 8; ++$i) { $table->addRow(); - $table->addCell(2000)->addText(htmlspecialchars("Cell {$i}")); - $table->addCell(2000)->addText(htmlspecialchars("Cell {$i}")); - $table->addCell(2000)->addText(htmlspecialchars("Cell {$i}")); - $table->addCell(2000)->addText(htmlspecialchars("Cell {$i}")); - $text = (0== $i % 2) ? 'X' : ''; - $table->addCell(500)->addText(htmlspecialchars($text)); + $table->addCell(2000)->addText("Cell {$i}"); + $table->addCell(2000)->addText("Cell {$i}"); + $table->addCell(2000)->addText("Cell {$i}"); + $table->addCell(2000)->addText("Cell {$i}"); + $text = (0 == $i % 2) ? 'X' : ''; + $table->addCell(500)->addText($text); } -// 3. colspan (gridSpan) and rowspan (vMerge) +/* + * 3. colspan (gridSpan) and rowspan (vMerge) + * ------------------------- + * | A | B | C | + * |-----|-----------| | + * | D | | + * ------|-----------| | + * | E | F | G | | + * ------------------------- + */ $section->addPageBreak(); -$section->addText(htmlspecialchars('Table with colspan and rowspan'), $header); +$section->addText('Table with colspan and rowspan', $header); + +$fancyTableStyle = ['borderSize' => 6, 'borderColor' => '999999']; +$cellRowSpan = ['vMerge' => 'restart', 'valign' => 'center', 'bgColor' => 'FFFF00']; +$cellRowContinue = ['vMerge' => 'continue']; +$cellColSpan = ['gridSpan' => 2, 'valign' => 'center']; +$cellHCentered = ['alignment' => PhpOffice\PhpWord\SimpleType\Jc::CENTER]; +$cellVCentered = ['valign' => 'center']; + +$spanTableStyleName = 'Colspan Rowspan'; +$phpWord->addTableStyle($spanTableStyleName, $fancyTableStyle); +$table = $section->addTable($spanTableStyleName); + +$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']); + +/* + * 4. colspan (gridSpan) and rowspan (vMerge) + * --------------------- + * | | B | 1 | + * | A | |----| + * | | | 2 | + * | |---|----|----| + * | | C | D | 3 | + * --------------------- + * @see https://github.com/PHPOffice/PHPWord/issues/806 + */ -$styleTable = array('borderSize' => 6, 'borderColor' => '999999'); -$cellRowSpan = array('vMerge' => 'restart', 'valign' => 'center', 'bgColor' => 'FFFF00'); -$cellRowContinue = array('vMerge' => 'continue'); -$cellColSpan = array('gridSpan' => 2, 'valign' => 'center'); -$cellHCentered = array('align' => 'center'); -$cellVCentered = array('valign' => 'center'); +$section->addPageBreak(); +$section->addText('Table with colspan and rowspan', $header); +$styleTable = ['borderSize' => 6, 'borderColor' => '999999']; $phpWord->addTableStyle('Colspan Rowspan', $styleTable); $table = $section->addTable('Colspan Rowspan'); -$table->addRow(); +$row = $table->addRow(); +$row->addCell(1000, ['vMerge' => 'restart'])->addText('A'); +$row->addCell(1000, ['gridSpan' => 2, 'vMerge' => 'restart'])->addText('B'); +$row->addCell(1000)->addText('1'); -$cell1 = $table->addCell(2000, $cellRowSpan); -$textrun1 = $cell1->addTextRun($cellHCentered); -$textrun1->addText(htmlspecialchars('A')); -$textrun1->addFootnote()->addText(htmlspecialchars('Row span')); +$row = $table->addRow(); +$row->addCell(1000, ['vMerge' => 'continue']); +$row->addCell(1000, ['vMerge' => 'continue', 'gridSpan' => 2]); +$row->addCell(1000)->addText('2'); -$cell2 = $table->addCell(4000, $cellColSpan); -$textrun2 = $cell2->addTextRun($cellHCentered); -$textrun2->addText(htmlspecialchars('B')); -$textrun2->addFootnote()->addText(htmlspecialchars('Colspan span')); +$row = $table->addRow(); +$row->addCell(1000, ['vMerge' => 'continue']); +$row->addCell(1000)->addText('C'); +$row->addCell(1000)->addText('D'); +$row->addCell(1000)->addText('3'); -$table->addCell(2000, $cellRowSpan)->addText(htmlspecialchars('E'), null, $cellHCentered); +// 5. Nested table -$table->addRow(); -$table->addCell(null, $cellRowContinue); -$table->addCell(2000, $cellVCentered)->addText(htmlspecialchars('C'), null, $cellHCentered); -$table->addCell(2000, $cellVCentered)->addText(htmlspecialchars('D'), null, $cellHCentered); -$table->addCell(null, $cellRowContinue); +$section->addTextBreak(2); +$section->addText('Nested table in a centered and 50% width table.', $header); + +$table = $section->addTable(['width' => 50 * 50, 'unit' => 'pct', 'alignment' => PhpOffice\PhpWord\SimpleType\JcTable::CENTER]); +$cell = $table->addRow()->addCell(); +$cell->addText('This cell contains nested table.'); +$innerCell = $cell->addTable(['alignment' => PhpOffice\PhpWord\SimpleType\JcTable::CENTER])->addRow()->addCell(); +$innerCell->addText('Inside nested table'); -// 4. Nested table +// 6. Table with floating position $section->addTextBreak(2); -$section->addText(htmlspecialchars('Nested table in a centered and 50% width table.'), $header); +$section->addText('Table with floating positioning.', $header); -$table = $section->addTable(array('width' => 50 * 50, 'unit' => 'pct', 'align' => 'center')); +$table = $section->addTable(['borderSize' => 6, 'borderColor' => '999999', 'position' => ['vertAnchor' => TablePosition::VANCHOR_TEXT, 'bottomFromText' => Converter::cmToTwip(1)]]); $cell = $table->addRow()->addCell(); -$cell->addText(htmlspecialchars('This cell contains nested table.')); -$innerCell = $cell->addTable(array('align' => 'center'))->addRow()->addCell(); -$innerCell->addText(htmlspecialchars('Inside nested table')); +$cell->addText('This is a single cell.'); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_10_EastAsianFontStyle.php b/samples/Sample_10_EastAsianFontStyle.php index f3553bfef2..f730ab510f 100644 --- a/samples/Sample_10_EastAsianFontStyle.php +++ b/samples/Sample_10_EastAsianFontStyle.php @@ -1,13 +1,14 @@ addSection(); -$header = array('size' => 16, 'bold' => true); +$header = ['size' => 16, 'bold' => true]; //1.Use EastAisa FontStyle -$section->addText(htmlspecialchars('中文楷体样式测试'), array('name' => '楷体', 'size' => 16, 'color' => '1B2232')); +$section->addText('中文楷体样式测试', ['name' => '楷体', 'size' => 16, 'color' => '1B2232', 'lang' => ['latin' => 'en-US', 'eastAsia' => 'zh-CN']]); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_11_ReadWord2007.php b/samples/Sample_11_ReadWord2007.php index c0b54c7a49..72cdfac625 100644 --- a/samples/Sample_11_ReadWord2007.php +++ b/samples/Sample_11_ReadWord2007.php @@ -1,4 +1,5 @@ 'docx', 'ODText' => 'odt', 'RTF' => 'rtf'); -foreach ($writers as $writer => $extension) { - echo date('H:i:s'), " Write to {$writer} format", EOL; - $xmlWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, $writer); - $xmlWriter->save("{$name}.{$extension}"); - rename("{$name}.{$extension}", "results/{$name}.{$extension}"); +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; } - -include_once 'Sample_Footer.php'; diff --git a/samples/Sample_12_HeaderFooter.php b/samples/Sample_12_HeaderFooter.php index dc4fc129cb..393e9d1032 100644 --- a/samples/Sample_12_HeaderFooter.php +++ b/samples/Sample_12_HeaderFooter.php @@ -1,9 +1,10 @@ addSection(); @@ -15,50 +16,47 @@ $table->addRow(); $cell = $table->addCell(4500); $textrun = $cell->addTextRun(); -$textrun->addText(htmlspecialchars('This is the header with ')); -$textrun->addLink('http://google.com/', htmlspecialchars('link to Google')); -$table->addCell(4500)->addImage( - 'resources/PhpWord.png', - array('width' => 80, 'height' => 80, 'align' => 'right') -); +$textrun->addText('This is the header with '); +$textrun->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord on GitHub'); +$table->addCell(4500)->addImage(__DIR__ . '/resources/PhpWord.png', ['width' => 80, 'height' => 80, 'alignment' => PhpOffice\PhpWord\SimpleType\Jc::END]); // Add header for all other pages $subsequent = $section->addHeader(); -$subsequent->addText(htmlspecialchars('Subsequent pages in Section 1 will Have this!')); -$subsequent->addImage('resources/_mars.jpg', array('width' => 80, 'height' => 80)); +$subsequent->addText('Subsequent pages in Section 1 will Have this!'); +$subsequent->addImage(__DIR__ . '/resources/_mars.jpg', ['width' => 80, 'height' => 80]); // Add footer $footer = $section->addFooter(); -$footer->addPreserveText(htmlspecialchars('Page {PAGE} of {NUMPAGES}.'), array('align' => 'center')); -$footer->addLink('http://google.com/', htmlspecialchars('Direct Google')); +$footer->addPreserveText('Page {PAGE} of {NUMPAGES}.', null, ['alignment' => PhpOffice\PhpWord\SimpleType\Jc::CENTER]); +$footer->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord on GitHub'); // Write some text $section->addTextBreak(); -$section->addText(htmlspecialchars('Some text...')); +$section->addText('Some text...'); // Create a second page $section->addPageBreak(); // Write some text $section->addTextBreak(); -$section->addText(htmlspecialchars('Some text...')); +$section->addText('Some text...'); // Create a third page $section->addPageBreak(); // Write some text $section->addTextBreak(); -$section->addText(htmlspecialchars('Some text...')); +$section->addText('Some text...'); // New portrait section $section2 = $phpWord->addSection(); $sec2Header = $section2->addHeader(); -$sec2Header->addText(htmlspecialchars('All pages in Section 2 will Have this!')); +$sec2Header->addText('All pages in Section 2 will Have this!'); // Write some text $section2->addTextBreak(); -$section2->addText(htmlspecialchars('Some text...')); +$section2->addText('Some text...'); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_13_Images.php b/samples/Sample_13_Images.php index d13912119b..a8ab87ce8c 100644 --- a/samples/Sample_13_Images.php +++ b/samples/Sample_13_Images.php @@ -1,79 +1,101 @@ addSection(); -$section->addText(htmlspecialchars('Local image without any styles:')); -$section->addImage('resources/_mars.jpg'); -$section->addTextBreak(2); +$section->addText('Local image without any styles:'); +$section->addImage(__DIR__ . '/resources/_mars.jpg'); -$section->addText(htmlspecialchars('Local image with styles:')); -$section->addImage('resources/_earth.jpg', array('width' => 210, 'height' => 210, 'align' => 'center')); -$section->addTextBreak(2); +printSeparator($section); +$section->addText('Local image with styles:'); +$section->addImage(__DIR__ . '/resources/_earth.jpg', ['width' => 210, 'height' => 210, 'alignment' => PhpOffice\PhpWord\SimpleType\Jc::CENTER]); // Remote image +printSeparator($section); $source = 'http://php.net/images/logos/php-med-trans-light.gif'; -$section->addText(htmlspecialchars("Remote image from: {$source}")); +$section->addText("Remote image from: {$source}"); $section->addImage($source); +// Image from string +printSeparator($section); +$source = __DIR__ . '/resources/_mars.jpg'; +$fileContent = file_get_contents($source); +$section->addText('Image from string'); +$section->addImage($fileContent); + //Wrapping style -$text = str_repeat('Hello World! ', 15); -$wrappingStyles = array('inline', 'behind', 'infront', 'square', 'tight'); +printSeparator($section); +$text = str_repeat('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ', 2); +$wrappingStyles = ['inline', 'behind', 'infront', 'square', 'tight']; foreach ($wrappingStyles as $wrappingStyle) { - $section->addTextBreak(5); - $section->addText(htmlspecialchars("Wrapping style {$wrappingStyle}")); + $section->addText("Wrapping style {$wrappingStyle}"); $section->addImage( - 'resources/_earth.jpg', - array( - 'positioning' => 'relative', - 'marginTop' => -1, - 'marginLeft' => 1, - 'width' => 80, - 'height' => 80, + __DIR__ . '/resources/_earth.jpg', + [ + 'positioning' => 'relative', + 'marginTop' => -1, + 'marginLeft' => 1, + 'width' => 80, + 'height' => 80, 'wrappingStyle' => $wrappingStyle, - ) + 'wrapDistanceRight' => Converter::cmToPoint(1), + 'wrapDistanceBottom' => Converter::cmToPoint(1), + ] ); - $section->addText(htmlspecialchars($text)); + $section->addText($text); + printSeparator($section); } //Absolute positioning -$section->addTextBreak(3); -$section->addText(htmlspecialchars('Absolute positioning: see top right corner of page')); +$section->addText('Absolute positioning: see top right corner of page'); $section->addImage( - 'resources/_mars.jpg', - array( - 'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(3), - 'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(3), - 'positioning' => \PhpOffice\PhpWord\Style\Image::POSITION_ABSOLUTE, - 'posHorizontal' => \PhpOffice\PhpWord\Style\Image::POSITION_HORIZONTAL_RIGHT, - 'posHorizontalRel' => \PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_PAGE, - 'posVerticalRel' => \PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_PAGE, - 'marginLeft' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(15.5), - 'marginTop' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(1.55), - ) + __DIR__ . '/resources/_mars.jpg', + [ + 'width' => Converter::cmToPixel(3), + 'height' => Converter::cmToPixel(3), + 'positioning' => PhpOffice\PhpWord\Style\Image::POSITION_ABSOLUTE, + 'posHorizontal' => PhpOffice\PhpWord\Style\Image::POSITION_HORIZONTAL_RIGHT, + 'posHorizontalRel' => PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_PAGE, + 'posVerticalRel' => PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_PAGE, + 'marginLeft' => Converter::cmToPixel(15.5), + 'marginTop' => Converter::cmToPixel(1.55), + ] ); //Relative positioning -$section->addTextBreak(3); -$section->addText(htmlspecialchars('Relative positioning: Horizontal position center relative to column,')); -$section->addText(htmlspecialchars('Vertical position top relative to line')); +printSeparator($section); +$section->addText('Relative positioning: Horizontal position center relative to column,'); +$section->addText('Vertical position top relative to line'); $section->addImage( - 'resources/_mars.jpg', - array( - 'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(3), - 'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(3), - 'positioning' => \PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE, - 'posHorizontal' => \PhpOffice\PhpWord\Style\Image::POSITION_HORIZONTAL_CENTER, - 'posHorizontalRel' => \PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_COLUMN, - 'posVertical' => \PhpOffice\PhpWord\Style\Image::POSITION_VERTICAL_TOP, - 'posVerticalRel' => \PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_LINE, - ) + __DIR__ . '/resources/_mars.jpg', + [ + 'width' => Converter::cmToPixel(3), + 'height' => Converter::cmToPixel(3), + 'positioning' => PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE, + 'posHorizontal' => PhpOffice\PhpWord\Style\Image::POSITION_HORIZONTAL_CENTER, + 'posHorizontalRel' => PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_COLUMN, + 'posVertical' => PhpOffice\PhpWord\Style\Image::POSITION_VERTICAL_TOP, + 'posVerticalRel' => PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_LINE, + ] ); +function printSeparator(Section $section): void +{ + $section->addTextBreak(); + $lineStyle = ['weight' => 0.2, 'width' => 150, 'height' => 0, 'align' => 'center']; + $section->addLine($lineStyle); + $section->addTextBreak(2); +} + // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); if (!CLI) { diff --git a/samples/Sample_14_ListItem.php b/samples/Sample_14_ListItem.php index 3a29e3fdee..f82c3ec064 100644 --- a/samples/Sample_14_ListItem.php +++ b/samples/Sample_14_ListItem.php @@ -1,92 +1,99 @@ addSection(); +// Define styles +$fontStyleName = 'myOwnStyle'; +$phpWord->addFontStyle($fontStyleName, ['color' => 'FF0000']); -// Style definition +$paragraphStyleName = 'P-Style'; +$phpWord->addParagraphStyle($paragraphStyleName, ['spaceAfter' => 95]); -$phpWord->addFontStyle('myOwnStyle', array('color' => 'FF0000')); -$phpWord->addParagraphStyle('P-Style', array('spaceAfter' => 95)); +$multilevelNumberingStyleName = 'multilevel'; $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), - ), - ) + $multilevelNumberingStyleName, + [ + 'type' => 'multilevel', + 'levels' => [ + ['format' => 'decimal', 'text' => '%1.', 'left' => 360, 'hanging' => 360, 'tabPos' => 360], + ['format' => 'upperLetter', 'text' => '%2.', 'left' => 720, 'hanging' => 360, 'tabPos' => 720], + ], + ] ); -$predefinedMultilevel = array('listType' => \PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER_NESTED); -// Lists +$predefinedMultilevelStyle = ['listType' => PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER_NESTED]; + +// New section +$section = $phpWord->addSection(); -$section->addText(htmlspecialchars('Multilevel list.')); -$section->addListItem(htmlspecialchars('List Item I'), 0, null, 'multilevel'); -$section->addListItem(htmlspecialchars('List Item I.a'), 1, null, 'multilevel'); -$section->addListItem(htmlspecialchars('List Item I.b'), 1, null, 'multilevel'); -$section->addListItem(htmlspecialchars('List Item II'), 0, null, 'multilevel'); -$section->addListItem(htmlspecialchars('List Item II.a'), 1, null, 'multilevel'); -$section->addListItem(htmlspecialchars('List Item III'), 0, null, 'multilevel'); +// Lists +$section->addText('Multilevel list.'); +$section->addListItem('List Item I', 0, null, $multilevelNumberingStyleName); +$section->addListItem('List Item I.a', 1, null, $multilevelNumberingStyleName); +$section->addListItem('List Item I.b', 1, null, $multilevelNumberingStyleName); +$section->addListItem('List Item II', 0, null, $multilevelNumberingStyleName); +$section->addListItem('List Item II.a', 1, null, $multilevelNumberingStyleName); +$section->addListItem('List Item III', 0, null, $multilevelNumberingStyleName); $section->addTextBreak(2); -$section->addText(htmlspecialchars('Basic simple bulleted list.')); -$section->addListItem(htmlspecialchars('List Item 1')); -$section->addListItem(htmlspecialchars('List Item 2')); -$section->addListItem(htmlspecialchars('List Item 3')); +$section->addText('Basic simple bulleted list.'); +$section->addListItem('List Item 1'); +$section->addListItem('List Item 2'); +$section->addListItem('List Item 3'); $section->addTextBreak(2); -$section->addText(htmlspecialchars('Continue from multilevel list above.')); -$section->addListItem(htmlspecialchars('List Item IV'), 0, null, 'multilevel'); -$section->addListItem(htmlspecialchars('List Item IV.a'), 1, null, 'multilevel'); +$section->addText('Continue from multilevel list above.'); +$section->addListItem('List Item IV', 0, null, $multilevelNumberingStyleName); +$section->addListItem('List Item IV.a', 1, null, $multilevelNumberingStyleName); $section->addTextBreak(2); -$section->addText(htmlspecialchars('Multilevel predefined list.')); -$section->addListItem(htmlspecialchars('List Item 1'), 0, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); -$section->addListItem(htmlspecialchars('List Item 2'), 0, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); -$section->addListItem(htmlspecialchars('List Item 3'), 1, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); -$section->addListItem(htmlspecialchars('List Item 4'), 1, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); -$section->addListItem(htmlspecialchars('List Item 5'), 2, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); -$section->addListItem(htmlspecialchars('List Item 6'), 1, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); -$section->addListItem(htmlspecialchars('List Item 7'), 0, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); +$section->addText('Multilevel predefined list.'); +$section->addListItem('List Item 1', 0, $fontStyleName, $predefinedMultilevelStyle, $paragraphStyleName); +$section->addListItem('List Item 2', 0, $fontStyleName, $predefinedMultilevelStyle, $paragraphStyleName); +$section->addListItem('List Item 3', 1, $fontStyleName, $predefinedMultilevelStyle, $paragraphStyleName); +$section->addListItem('List Item 4', 1, $fontStyleName, $predefinedMultilevelStyle, $paragraphStyleName); +$section->addListItem('List Item 5', 2, $fontStyleName, $predefinedMultilevelStyle, $paragraphStyleName); +$section->addListItem('List Item 6', 1, $fontStyleName, $predefinedMultilevelStyle, $paragraphStyleName); +$section->addListItem('List Item 7', 0, $fontStyleName, $predefinedMultilevelStyle, $paragraphStyleName); $section->addTextBreak(2); -$section->addText(htmlspecialchars('List with inline formatting.')); +$section->addText('List with inline formatting.'); $listItemRun = $section->addListItemRun(); -$listItemRun->addText(htmlspecialchars('List item 1')); -$listItemRun->addText(htmlspecialchars(' in bold'), array('bold' => true)); +$listItemRun->addText('List item 1'); +$listItemRun->addText(' in bold', ['bold' => true]); +$listItemRun = $section->addListItemRun(1, $predefinedMultilevelStyle, $paragraphStyleName); +$listItemRun->addText('List item 2'); +$listItemRun->addText(' in italic', ['italic' => true]); +$footnote = $listItemRun->addFootnote(); +$footnote->addText('this is a footnote on a list item'); $listItemRun = $section->addListItemRun(); -$listItemRun->addText(htmlspecialchars('List item 2')); -$listItemRun->addText(htmlspecialchars(' in italic'), array('italic' => true)); -$listItemRun = $section->addListItemRun(); -$listItemRun->addText(htmlspecialchars('List item 3')); -$listItemRun->addText(htmlspecialchars(' underlined'), array('underline' => 'dash')); +$listItemRun->addText('List item 3'); +$listItemRun->addText(' underlined', ['underline' => 'dash']); $section->addTextBreak(2); // Numbered heading - +$headingNumberingStyleName = 'headingNumbering'; $phpWord->addNumberingStyle( - 'headingNumbering', - 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'), - ), - ) + $headingNumberingStyleName, + ['type' => 'multilevel', + 'levels' => [ + ['pStyle' => 'Heading1', 'format' => 'decimal', 'text' => '%1'], + ['pStyle' => 'Heading2', 'format' => 'decimal', 'text' => '%1.%2'], + ['pStyle' => 'Heading3', 'format' => 'decimal', 'text' => '%1.%2.%3'], + ], + ] ); -$phpWord->addTitleStyle(1, array('size' => 16), array('numStyle' => 'headingNumbering', 'numLevel' => 0)); -$phpWord->addTitleStyle(2, array('size' => 14), array('numStyle' => 'headingNumbering', 'numLevel' => 1)); -$phpWord->addTitleStyle(3, array('size' => 12), array('numStyle' => 'headingNumbering', 'numLevel' => 2)); +$phpWord->addTitleStyle(1, ['size' => 16], ['numStyle' => $headingNumberingStyleName, 'numLevel' => 0]); +$phpWord->addTitleStyle(2, ['size' => 14], ['numStyle' => $headingNumberingStyleName, 'numLevel' => 1]); +$phpWord->addTitleStyle(3, ['size' => 12], ['numStyle' => $headingNumberingStyleName, 'numLevel' => 2]); -$section->addTitle(htmlspecialchars('Heading 1'), 1); -$section->addTitle(htmlspecialchars('Heading 2'), 2); -$section->addTitle(htmlspecialchars('Heading 3'), 3); +$section->addTitle('Heading 1', 1); +$section->addTitle('Heading 2', 2); +$section->addTitle('Heading 3', 3); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_15_Link.php b/samples/Sample_15_Link.php index dd06670e65..43994deaa2 100644 --- a/samples/Sample_15_Link.php +++ b/samples/Sample_15_Link.php @@ -1,24 +1,27 @@ addLinkStyle($linkFontStyleName, ['bold' => true, 'color' => '808000']); -// Begin code +// New section $section = $phpWord->addSection(); // Add hyperlink elements $section->addLink( - 'http://www.google.com/', - htmlspecialchars('Best search engine'), - array('color' => '0000FF', 'underline' => \PhpOffice\PhpWord\Style\Font::UNDERLINE_SINGLE) + 'https://github.com/PHPOffice/PHPWord', + 'PHPWord on GitHub', + ['color' => '0000FF', 'underline' => PhpOffice\PhpWord\Style\Font::UNDERLINE_SINGLE] ); $section->addTextBreak(2); - -$phpWord->addLinkStyle('myOwnLinkStyle', array('bold' => true, 'color' => '808000')); -$section->addLink('http://www.bing.com/', null, 'myOwnLinkStyle'); -$section->addLink('http://www.yahoo.com/', null, 'myOwnLinkStyle'); +$section->addLink('http://www.bing.com/', null, $linkFontStyleName); +$section->addLink('http://www.yahoo.com/', null, $linkFontStyleName); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_16_Object.php b/samples/Sample_16_Object.php index 2a216c2378..d3912a6c50 100644 --- a/samples/Sample_16_Object.php +++ b/samples/Sample_16_Object.php @@ -1,15 +1,16 @@ addSection(); -$section->addText(htmlspecialchars('You can open this OLE object by double clicking on the icon:')); +$section->addText('You can open this OLE object by double clicking on the icon:'); $section->addTextBreak(2); -$section->addObject('resources/_sheet.xls'); +$section->addOLEObject(__DIR__ . '/resources/_sheet.xls'); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_17_TitleTOC.php b/samples/Sample_17_TitleTOC.php index 8f8889b83c..3102ebda83 100644 --- a/samples/Sample_17_TitleTOC.php +++ b/samples/Sample_17_TitleTOC.php @@ -1,70 +1,70 @@ getSettings()->setUpdateFields(true); -// Begin code +// New section $section = $phpWord->addSection(); -// Define the TOC font style -$fontStyle = array('spaceAfter' => 60, 'size' => 12); -$fontStyle2 = array('size' => 10); - -// Add title styles -$phpWord->addTitleStyle(1, array('size' => 20, 'color' => '333333', 'bold' => true)); -$phpWord->addTitleStyle(2, array('size' => 16, 'color' => '666666')); -$phpWord->addTitleStyle(3, array('size' => 14, 'italic' => true)); -$phpWord->addTitleStyle(4, array('size' => 12)); +// Define styles +$fontStyle12 = ['spaceAfter' => 60, 'size' => 12]; +$fontStyle10 = ['size' => 10]; +$phpWord->addTitleStyle(null, ['size' => 22, 'bold' => true]); +$phpWord->addTitleStyle(1, ['size' => 20, 'color' => '333333', 'bold' => true]); +$phpWord->addTitleStyle(2, ['size' => 16, 'color' => '666666']); +$phpWord->addTitleStyle(3, ['size' => 14, 'italic' => true]); +$phpWord->addTitleStyle(4, ['size' => 12]); // Add text elements -$section->addText(htmlspecialchars('Table of contents 1')); +$section->addTitle('Table of contents 1', 0); $section->addTextBreak(2); // Add TOC #1 -$toc = $section->addTOC($fontStyle); +$toc = $section->addTOC($fontStyle12); $section->addTextBreak(2); // Filler -$section->addText(htmlspecialchars('Text between TOC')); +$section->addText('Text between TOC'); $section->addTextBreak(2); // Add TOC #1 -$section->addText(htmlspecialchars('Table of contents 2')); +$section->addText('Table of contents 2'); $section->addTextBreak(2); -$toc2 = $section->addTOC($fontStyle2); +$toc2 = $section->addTOC($fontStyle10); $toc2->setMinDepth(2); $toc2->setMaxDepth(3); - // Add Titles $section->addPageBreak(); -$section->addTitle(htmlspecialchars('Foo & Bar'), 1); -$section->addText(htmlspecialchars('Some text...')); +$section->addTitle('Foo n Bar', 1); +$section->addText('Some text...'); $section->addTextBreak(2); -$section->addTitle(htmlspecialchars('I am a Subtitle of Title 1'), 2); +$section->addTitle('I am a Subtitle of Title 1', 2); $section->addTextBreak(2); -$section->addText(htmlspecialchars('Some more text...')); +$section->addText('Some more text...'); $section->addTextBreak(2); -$section->addTitle(htmlspecialchars('Another Title (Title 2)'), 1); -$section->addText(htmlspecialchars('Some text...')); +$section->addTitle('Another Title (Title 2)', 1); +$section->addText('Some text...'); $section->addPageBreak(); -$section->addTitle(htmlspecialchars('I am Title 3'), 1); -$section->addText(htmlspecialchars('And more text...')); +$section->addTitle('I am Title 3', 1); +$section->addText('And more text...'); $section->addTextBreak(2); -$section->addTitle(htmlspecialchars('I am a Subtitle of Title 3'), 2); -$section->addText(htmlspecialchars('Again and again, more text...')); -$section->addTitle(htmlspecialchars('Subtitle 3.1.1'), 3); -$section->addText(htmlspecialchars('Text')); -$section->addTitle(htmlspecialchars('Subtitle 3.1.1.1'), 4); -$section->addText(htmlspecialchars('Text')); -$section->addTitle(htmlspecialchars('Subtitle 3.1.1.2'), 4); -$section->addText(htmlspecialchars('Text')); -$section->addTitle(htmlspecialchars('Subtitle 3.1.2'), 3); -$section->addText(htmlspecialchars('Text')); +$section->addTitle('I am a Subtitle of Title 3', 2); +$section->addText('Again and again, more text...'); +$section->addTitle('Subtitle 3.1.1', 3); +$section->addText('Text'); +$section->addTitle('Subtitle 3.1.1.1', 4); +$section->addText('Text'); +$section->addTitle('Subtitle 3.1.1.2', 4); +$section->addText('Text'); +$section->addTitle('Subtitle 3.1.2', 3); +$section->addText('Text'); echo date('H:i:s'), ' Note: Please refresh TOC manually.', EOL; diff --git a/samples/Sample_18_Watermark.php b/samples/Sample_18_Watermark.php index f630bdf22d..0b3ae34f83 100644 --- a/samples/Sample_18_Watermark.php +++ b/samples/Sample_18_Watermark.php @@ -1,16 +1,16 @@ addSection(); $header = $section->addHeader(); -$header->addWatermark('resources/_earth.jpg', array('marginTop' => 200, 'marginLeft' => 55)); -$section->addText(htmlspecialchars('The header reference to the current section includes a watermark image.')); +$header->addWatermark(__DIR__ . '/resources/_earth.jpg', ['marginTop' => 200, 'marginLeft' => 55]); +$section->addText('The header reference to the current section includes a watermark image.'); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_19_TextBreak.php b/samples/Sample_19_TextBreak.php index fc0e5419b0..0b41110b18 100644 --- a/samples/Sample_19_TextBreak.php +++ b/samples/Sample_19_TextBreak.php @@ -1,29 +1,36 @@ 24]; + +$paragraphStyle24 = ['spacing' => 240, 'size' => 24]; -// Begin code -$fontStyle = array('size' => 24); -$paragraphStyle = array('spacing' => 240, 'size' => 24); -$phpWord->addFontStyle('fontStyle', array('size' => 9)); -$phpWord->addParagraphStyle('paragraphStyle', array('spacing' => 480)); -$fontStyle = array('size' => 24); +$fontStyleName = 'fontStyle'; +$phpWord->addFontStyle($fontStyleName, ['size' => 9]); +$paragraphStyleName = 'paragraphStyle'; +$phpWord->addParagraphStyle($paragraphStyleName, ['spacing' => 480]); + +// New section $section = $phpWord->addSection(); -$section->addText(htmlspecialchars('Text break with no style:')); + +$section->addText('Text break with no style:'); $section->addTextBreak(); -$section->addText(htmlspecialchars('Text break with defined font style:')); -$section->addTextBreak(1, 'fontStyle'); -$section->addText(htmlspecialchars('Text break with defined paragraph style:')); -$section->addTextBreak(1, null, 'paragraphStyle'); -$section->addText(htmlspecialchars('Text break with inline font style:')); -$section->addTextBreak(1, $fontStyle); -$section->addText(htmlspecialchars('Text break with inline paragraph style:')); -$section->addTextBreak(1, null, $paragraphStyle); -$section->addText(htmlspecialchars('Done.')); +$section->addText('Text break with defined font style:'); +$section->addTextBreak(1, $fontStyleName); +$section->addText('Text break with defined paragraph style:'); +$section->addTextBreak(1, null, $paragraphStyleName); +$section->addText('Text break with inline font style:'); +$section->addTextBreak(1, $fontStyle24); +$section->addText('Text break with inline paragraph style:'); +$section->addTextBreak(1, null, $paragraphStyle24); +$section->addText('Done.'); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_20_BGColor.php b/samples/Sample_20_BGColor.php index ebee8544fa..e0ce4d6436 100644 --- a/samples/Sample_20_BGColor.php +++ b/samples/Sample_20_BGColor.php @@ -1,20 +1,20 @@ addSection(); $section->addText( - htmlspecialchars('This is some text highlighted using fgColor (limited to 15 colors) '), - array('fgColor' => \PhpOffice\PhpWord\Style\Font::FGCOLOR_YELLOW) -); -$section->addText( - htmlspecialchars('This one uses bgColor and is using hex value (0xfbbb10)'), - array('bgColor' => 'fbbb10') + 'This is some text highlighted using fgColor (limited to 15 colors)', + ['fgColor' => PhpOffice\PhpWord\Style\Font::FGCOLOR_YELLOW] ); -$section->addText(htmlspecialchars('Compatible with font colors'), array('color' => '0000ff', 'bgColor' => 'fbbb10')); +$section->addText('This one uses bgColor and is using hex value (0xfbbb10)', ['bgColor' => 'fbbb10']); +$section->addText('Compatible with font colors', ['color' => '0000ff', 'bgColor' => 'fbbb10']); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_21_TableRowRules.php b/samples/Sample_21_TableRowRules.php index df3162c33c..0ddaa08faf 100644 --- a/samples/Sample_21_TableRowRules.php +++ b/samples/Sample_21_TableRowRules.php @@ -1,51 +1,44 @@ addSection(); -$section->addText(htmlspecialchars('By default, when you insert an image, it adds a textbreak after its content.')); -$section->addText( - htmlspecialchars('If we want a simple border around an image, we wrap the image inside a table->row->cell') -); +$section->addText('By default, when you insert an image, it adds a textbreak after its content.'); +$section->addText('If we want a simple border around an image, we wrap the image inside a table->row->cell'); $section->addText( - htmlspecialchars( - 'On the image with the red border, even if we set the row height to the height of the image, ' - . 'the textbreak is still there:' - ) + 'On the image with the red border, even if we set the row height to the height of the image, ' + . 'the textbreak is still there:' ); -$table1 = $section->addTable(array('cellMargin' => 0, 'cellMarginRight' => 0, 'cellMarginBottom' => 0, 'cellMarginLeft' => 0)); +$table1 = $section->addTable(['cellMargin' => 0, 'cellMarginRight' => 0, 'cellMarginBottom' => 0, 'cellMarginLeft' => 0]); $table1->addRow(3750); -$cell1 = $table1->addCell(null, array('valign' => 'top', 'borderSize' => 30, 'borderColor' => 'ff0000')); -$cell1->addImage('./resources/_earth.jpg', array('width' => 250, 'height' => 250, 'align' => 'center')); +$cell1 = $table1->addCell(null, ['valign' => 'top', 'borderSize' => 30, 'borderColor' => 'ff0000']); +$cell1->addImage(__DIR__ . '/resources/_earth.jpg', ['width' => 250, 'height' => 250, 'alignment' => PhpOffice\PhpWord\SimpleType\Jc::CENTER]); $section->addTextBreak(); -$section->addText( - htmlspecialchars( - "But if we set the rowStyle 'exactHeight' to true, the real row height is used, removing the textbreak:" - ) -); +$section->addText("But if we set the rowStyle 'exactHeight' to true, the real row height is used, removing the textbreak:"); $table2 = $section->addTable( - array( - 'cellMargin' => 0, - 'cellMarginRight' => 0, + [ + 'cellMargin' => 0, + 'cellMarginRight' => 0, 'cellMarginBottom' => 0, - 'cellMarginLeft' => 0, - ) + 'cellMarginLeft' => 0, + ] ); -$table2->addRow(3750, array('exactHeight' => true)); -$cell2 = $table2->addCell(null, array('valign' => 'top', 'borderSize' => 30, 'borderColor' => '00ff00')); -$cell2->addImage('./resources/_earth.jpg', array('width' => 250, 'height' => 250, 'align' => 'center')); +$table2->addRow(3750, ['exactHeight' => true]); +$cell2 = $table2->addCell(null, ['valign' => 'top', 'borderSize' => 30, 'borderColor' => '00ff00']); +$cell2->addImage(__DIR__ . '/resources/_earth.jpg', ['width' => 250, 'height' => 250, 'alignment' => PhpOffice\PhpWord\SimpleType\Jc::CENTER]); $section->addTextBreak(); -$section->addText( - htmlspecialchars('In this example, image is 250px height. Rows are calculated in twips, and 1px = 15twips.') -); -$section->addText(htmlspecialchars('So: $' . "table2->addRow(3750, array('exactHeight'=>true));")); +$section->addText('In this example, image is 250px height. Rows are calculated in twips, and 1px = 15twips.'); +$section->addText('So: $' . "table2->addRow(3750, array('exactHeight'=>true));"); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_22_CheckBox.php b/samples/Sample_22_CheckBox.php index b5d4a7a5a8..91685924b0 100644 --- a/samples/Sample_22_CheckBox.php +++ b/samples/Sample_22_CheckBox.php @@ -1,18 +1,21 @@ addSection(); -$section->addText(htmlspecialchars('Check box in section')); -$section->addCheckBox('chkBox1', htmlspecialchars('Checkbox 1')); -$section->addText(htmlspecialchars('Check box in table cell')); + +$section->addText('Check box in section'); +$section->addCheckBox('chkBox1', 'Checkbox 1'); +$section->addText('Check box in table cell'); $table = $section->addTable(); $table->addRow(); $cell = $table->addCell(); -$cell->addCheckBox('chkBox2', htmlspecialchars('Checkbox 2')); +$cell->addCheckBox('chkBox2', 'Checkbox 2'); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_23_TemplateBlock.php b/samples/Sample_23_TemplateBlock.php index 2b7e9f6856..4cc541ab3a 100644 --- a/samples/Sample_23_TemplateBlock.php +++ b/samples/Sample_23_TemplateBlock.php @@ -1,9 +1,10 @@ cloneBlock('CLONEME', 3); @@ -12,9 +13,9 @@ $templateProcessor->deleteBlock('DELETEME'); echo date('H:i:s'), ' Saving the result document...', EOL; -$templateProcessor->saveAs('results/Sample_23_TemplateBlock.docx'); +$templateProcessor->saveAs(__DIR__ . '/results/Sample_23_TemplateBlock.docx'); -echo getEndingNotes(array('Word2007' => 'docx')); +echo getEndingNotes(['Word2007' => 'docx'], __DIR__ . '/results/Sample_23_TemplateBlock.docx'); if (!CLI) { include_once 'Sample_Footer.php'; } diff --git a/samples/Sample_24_ReadODText.php b/samples/Sample_24_ReadODText.php index 42df23ae2b..1b3a43cd57 100644 --- a/samples/Sample_24_ReadODText.php +++ b/samples/Sample_24_ReadODText.php @@ -1,4 +1,5 @@ addSection(); // In section $textbox = $section->addTextBox( - array( - 'align' => 'center', - 'width' => 400, - 'height' => 150, - 'borderSize' => 1, + [ + 'alignment' => PhpOffice\PhpWord\SimpleType\Jc::CENTER, + 'width' => 400, + 'height' => 150, + 'borderSize' => 1, 'borderColor' => '#FF0000', - ) + ] ); -$textbox->addText(htmlspecialchars('Text box content in section.')); -$textbox->addText(htmlspecialchars('Another line.')); +$textbox->addText('Text box content in section.'); +$textbox->addText('Another line.'); $cell = $textbox->addTable()->addRow()->addCell(); -$cell->addText(htmlspecialchars('Table inside textbox')); +$cell->addText('Table inside textbox'); // Inside table $section->addTextBreak(2); $cell = $section->addTable()->addRow()->addCell(300); -$textbox = $cell->addTextBox(array('borderSize' => 1, 'borderColor' => '#0000FF', 'innerMargin' => 100)); -$textbox->addText(htmlspecialchars('Textbox inside table')); +$textbox = $cell->addTextBox(['borderSize' => 1, 'borderColor' => '#0000FF', 'innerMargin' => 100]); +$textbox->addText('Textbox inside table'); // Inside header with textrun $header = $section->addHeader(); -$textbox = $header->addTextBox(array('width' => 600, 'borderSize' => 1, 'borderColor' => '#00FF00')); +$textbox = $header->addTextBox(['width' => 600, 'borderSize' => 1, 'borderColor' => '#00FF00']); $textrun = $textbox->addTextRun(); -$textrun->addText(htmlspecialchars('TextBox in header. TextBox can contain a TextRun ')); -$textrun->addText(htmlspecialchars('with bold text'), array('bold' => true)); -$textrun->addText(htmlspecialchars(', ')); -$textrun->addLink('http://www.google.com/', htmlspecialchars('link')); -$textrun->addText(htmlspecialchars(', and image ')); -$textrun->addImage('resources/_earth.jpg', array('width' => 18, 'height' => 18)); -$textrun->addText(htmlspecialchars('.')); +$textrun->addText('TextBox in header. TextBox can contain a TextRun '); +$textrun->addText('with bold text', ['bold' => true]); +$textrun->addText(', '); +$textrun->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord on GitHub'); +$textrun->addText(', and image '); +$textrun->addImage(__DIR__ . '/resources/_earth.jpg', ['width' => 18, 'height' => 18]); +$textrun->addText('.'); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_26_Html.php b/samples/Sample_26_Html.php index 4235c94650..971c317c43 100644 --- a/samples/Sample_26_Html.php +++ b/samples/Sample_26_Html.php @@ -1,20 +1,99 @@ addParagraphStyle('Heading2', ['alignment' => 'center']); $section = $phpWord->addSection(); $html = '

Adding element via HTML

'; -$html .= '

Some well formed HTML snippet needs to be used

'; +$html .= '

Some well-formed HTML snippet needs to be used

'; $html .= '

With for example some1 inline formatting1

'; -$html .= '

Unordered (bulleted) list:

'; + +$html .= '

A link to Read the docs

'; + +$html .= '

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

'; + +$html .= '

Unordered (bulleted) list:

'; $html .= '
  • Item 1
  • Item 2
    • Item 2.1
    • Item 2.1
'; -$html .= '

Ordered (numbered) list:

'; -$html .= '
  1. Item 1
  2. Item 2
'; -\PhpOffice\PhpWord\Shared\Html::addHtml($section, $html); +$html .= '

1.5 line height with first line text indent:

'; +$html .= '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

'; + +$html .= '

centered title

'; + +$html .= '

Ordered (numbered) list:

'; +$html .= '
    +
  1. List 1 item 1

  2. +
  3. List 1 item 2
  4. +
      +
    1. sub list 1
    2. +
    3. sub list 2
    4. +
    +
  5. List 1 item 3
  6. +
+

A second list, numbering should restart

+
    +
  1. List 2 item 1
  2. +
  3. List 2 item 2
  4. +
  5. +
      +
    1. sub list 1
    2. +
    3. sub list 2
    4. +
    +
  6. +
  7. List 2 item 3
  8. +
      +
    1. sub list 1, restarts with a
    2. +
    3. sub list 2
    4. +
    +
'; + +$html .= '

List with formatted content:

'; +$html .= '
    +
  • + + big list item1 + +
  • +
  • + + list item2 in bold + +
  • +
'; + +$html .= '

A table with formatting:

'; +$html .= ' + + + + + + + + + + + +
header aheader bheader c
12
This is bold text6
'; + +$html .= '

Table inside another table:

'; +$html .= ' + + +
+ + +
column 1column 2
+
Cell in parent table
'; + +$html .= '

The text below is not visible, click on show/hide to reveil it:

'; +$html .= '

This is hidden text

'; + +PhpOffice\PhpWord\Shared\Html::addHtml($section, $html, false, false); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_27_Field.php b/samples/Sample_27_Field.php index 9bdbef8243..e1dbb4f258 100644 --- a/samples/Sample_27_Field.php +++ b/samples/Sample_27_Field.php @@ -1,28 +1,71 @@ 14]); -// Begin code +// New section $section = $phpWord->addSection(); +$section->addTitle('This page demos fields'); // Add Field elements // See Element/Field.php for all options -$section->addText(htmlspecialchars('Date field:')); -$section->addField('DATE', array('dateformat' => 'dddd d MMMM yyyy H:mm:ss'), array('PreserveFormat')); +$section->addText('Date field:'); +$section->addField('DATE', ['dateformat' => 'dddd d MMMM yyyy H:mm:ss'], ['PreserveFormat']); + +$section->addText('Style Ref field:'); +$section->addField('STYLEREF', ['StyleIdentifier' => 'Heading 1']); + +$section->addText('Page field:'); +$section->addField('PAGE', ['format' => 'Arabic']); + +$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(); +$textrun->addText('An index field is '); +$textrun->addField('XE', [], ['Italic'], 'My first index'); +$textrun->addText('here:'); + +$indexEntryText = new TextRun(); +$indexEntryText->addText('My '); +$indexEntryText->addText('bold index', ['bold' => true]); +$indexEntryText->addText(' entry'); + +$textrun = $section->addTextRun(); +$textrun->addText('A complex index field is '); +$textrun->addField('XE', [], ['Bold'], $indexEntryText); +$textrun->addText('here:'); + +$section->addText('The actual index:'); +$section->addField('INDEX', [], ['\\e " "'], 'right click to update the index'); + +$textrun = $section->addTextRun(['alignment' => PhpOffice\PhpWord\SimpleType\Jc::CENTER]); +$textrun->addText('This is the date of lunar calendar '); +$textrun->addField('DATE', ['dateformat' => 'd-M-yyyy H:mm:ss'], ['PreserveFormat', 'LunarCalendar']); +$textrun->addText(' written in a textrun.'); +$section->addTextBreak(); -$section->addText(htmlspecialchars('Page field:')); -$section->addField('PAGE', array('format' => 'ArabicDash')); +$macroText = new TextRun(); +$macroText->addText('Double click', ['bold' => true]); +$macroText->addText(' to '); +$macroText->addText('zoom to 100%', ['italic' => true]); -$section->addText(htmlspecialchars('Number of pages field:')); -$section->addField('NUMPAGES', array('format' => 'Arabic', 'numformat' => '0,00'), array('PreserveFormat')); +$section->addText('A macro button with styled text:'); +$section->addField('MACROBUTTON', ['macroname' => 'Zoom100'], [], $macroText); +$section->addTextBreak(); -$textrun = $section->addTextRun(array('align' => 'center')); -$textrun->addText(htmlspecialchars('This is the date of lunar calendar ')); -$textrun->addField('DATE', array('dateformat' => 'd-M-yyyy H:mm:ss'), array('PreserveFormat', 'LunarCalendar')); -$textrun->addText(htmlspecialchars(' written in a textrun.')); +$section->addText('A macro button with simple text:'); +$section->addField('MACROBUTTON', ['macroname' => 'Zoom100'], [], 'double click to zoom'); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_28_ReadRTF.php b/samples/Sample_28_ReadRTF.php index 76ac3d48bb..52aa74a9a5 100644 --- a/samples/Sample_28_ReadRTF.php +++ b/samples/Sample_28_ReadRTF.php @@ -1,4 +1,5 @@ addSection(); // Add Line elements // See Element/Line.php for all options -$section->addText(htmlspecialchars('Horizontal Line (Inline style):')); +$section->addText('Horizontal Line (Inline style):'); $section->addLine( - array( - 'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(4), - 'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(0), + [ + 'width' => PhpOffice\PhpWord\Shared\Converter::cmToPixel(4), + 'height' => PhpOffice\PhpWord\Shared\Converter::cmToPixel(0), 'positioning' => 'absolute', - ) + ] ); -$section->addText(htmlspecialchars('Vertical Line (Inline style):')); +$section->addText('Vertical Line (Inline style):'); $section->addLine( - array( - 'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(0), - 'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(1), + [ + 'width' => PhpOffice\PhpWord\Shared\Converter::cmToPixel(0), + 'height' => PhpOffice\PhpWord\Shared\Converter::cmToPixel(1), 'positioning' => 'absolute', - ) + ] ); // Two text break $section->addTextBreak(1); -$section->addText(htmlspecialchars('Positioned Line (red):')); +$section->addText('Positioned Line (red):'); $section->addLine( - array( - 'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(4), - 'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(1), - 'positioning' => 'absolute', + [ + 'width' => PhpOffice\PhpWord\Shared\Converter::cmToPixel(4), + 'height' => PhpOffice\PhpWord\Shared\Converter::cmToPixel(1), + 'positioning' => 'absolute', 'posHorizontalRel' => 'page', - 'posVerticalRel' => 'page', - 'marginLeft' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(10), - 'marginTop' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(8), - 'wrappingStyle' => \PhpOffice\PhpWord\Style\Image::WRAPPING_STYLE_SQUARE, - 'color' => 'red', - ) + 'posVerticalRel' => 'page', + 'marginLeft' => PhpOffice\PhpWord\Shared\Converter::cmToPixel(10), + 'marginTop' => PhpOffice\PhpWord\Shared\Converter::cmToPixel(8), + 'wrappingStyle' => PhpOffice\PhpWord\Style\Image::WRAPPING_STYLE_SQUARE, + 'color' => 'red', + ] ); -$section->addText(htmlspecialchars('Horizontal Formatted Line')); +$section->addText('Horizontal Formatted Line'); $section->addLine( - array( - 'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(15), - 'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(0), + [ + 'width' => PhpOffice\PhpWord\Shared\Converter::cmToPixel(15), + 'height' => PhpOffice\PhpWord\Shared\Converter::cmToPixel(0), 'positioning' => 'absolute', - 'beginArrow' => \PhpOffice\PhpWord\Style\Line::ARROW_STYLE_BLOCK, - 'endArrow' => \PhpOffice\PhpWord\Style\Line::ARROW_STYLE_OVAL, - 'dash' => \PhpOffice\PhpWord\Style\Line::DASH_STYLE_LONG_DASH_DOT_DOT, - 'weight' => 10, - ) + 'beginArrow' => PhpOffice\PhpWord\Style\Line::ARROW_STYLE_BLOCK, + 'endArrow' => PhpOffice\PhpWord\Style\Line::ARROW_STYLE_OVAL, + 'dash' => PhpOffice\PhpWord\Style\Line::DASH_STYLE_LONG_DASH_DOT_DOT, + 'weight' => 10, + ] ); // Save file diff --git a/samples/Sample_30_ReadHTML.php b/samples/Sample_30_ReadHTML.php index 029f8c8cfd..b6564eddc8 100644 --- a/samples/Sample_30_ReadHTML.php +++ b/samples/Sample_30_ReadHTML.php @@ -1,4 +1,5 @@ addTitleStyle(1, array('size' => 14, 'bold' => true)); - +// New section $section = $phpWord->addSection(); +// Define styles +$phpWord->addTitleStyle(1, ['size' => 14, 'bold' => true]); + // Arc -$section->addTitle(htmlspecialchars('Arc'), 1); +$section->addTitle('Arc', 1); $section->addShape( 'arc', - array( - 'points' => '-90 20', - 'frame' => array('width' => 120, 'height' => 120), - 'outline' => array('color' => '#333333', 'weight' => 2, 'startArrow' => 'oval', 'endArrow' => 'open'), - ) + [ + 'points' => '-90 20', + 'frame' => ['width' => 120, 'height' => 120], + 'outline' => ['color' => '#333333', 'weight' => 2, 'startArrow' => 'oval', 'endArrow' => 'open'], + ] ); // Curve -$section->addTitle(htmlspecialchars('Curve'), 1); +$section->addTitle('Curve', 1); $section->addShape( 'curve', - array( - 'points' => '1,100 200,1 1,50 200,50', + [ + 'points' => '1,100 200,1 1,50 200,50', 'connector' => 'elbow', - 'outline' => array( - 'color' => '#66cc00', - 'weight' => 2, - 'dash' => 'dash', + 'outline' => [ + 'color' => '#66cc00', + 'weight' => 2, + 'dash' => 'dash', 'startArrow' => 'diamond', - 'endArrow' => 'block', - ), - ) + 'endArrow' => 'block', + ], + ] ); // Line -$section->addTitle(htmlspecialchars('Line'), 1); +$section->addTitle('Line', 1); $section->addShape( 'line', - array( - 'points' => '1,1 150,30', - 'outline' => array( - 'color' => '#cc00ff', - 'line' => 'thickThin', - 'weight' => 3, + [ + 'points' => '1,1 150,30', + 'outline' => [ + 'color' => '#cc00ff', + 'line' => 'thickThin', + 'weight' => 3, 'startArrow' => 'oval', - 'endArrow' => 'classic', - ), - ) + 'endArrow' => 'classic', + ], + ] ); // Polyline -$section->addTitle(htmlspecialchars('Polyline'), 1); +$section->addTitle('Polyline', 1); $section->addShape( 'polyline', - array( - 'points' => '1,30 20,10 55,20 75,10 100,40 115,50, 120,15 200,50', - 'outline' => array('color' => '#cc6666', 'weight' => 2, 'startArrow' => 'none', 'endArrow' => 'classic'), - ) + [ + 'points' => '1,30 20,10 55,20 75,10 100,40 115,50, 120,15 200,50', + 'outline' => ['color' => '#cc6666', 'weight' => 2, 'startArrow' => 'none', 'endArrow' => 'classic'], + ] ); // Rectangle -$section->addTitle(htmlspecialchars('Rectangle'), 1); +$section->addTitle('Rectangle', 1); $section->addShape( 'rect', - array( + [ 'roundness' => 0.2, - 'frame' => array('width' => 100, 'height' => 100, 'left' => 1, 'top' => 1), - 'fill' => array('color' => '#FFCC33'), - 'outline' => array('color' => '#990000', 'weight' => 1), - 'shadow' => array(), - ) + 'frame' => ['width' => 100, 'height' => 100, 'left' => 1, 'top' => 1], + 'fill' => ['color' => '#FFCC33'], + 'outline' => ['color' => '#990000', 'weight' => 1], + 'shadow' => [], + ] ); // Oval -$section->addTitle(htmlspecialchars('Oval'), 1); +$section->addTitle('Oval', 1); $section->addShape( 'oval', - array( - 'frame' => array('width' => 100, 'height' => 70, 'left' => 1, 'top' => 1), - 'fill' => array('color' => '#33CC99'), - 'outline' => array('color' => '#333333', 'weight' => 2), - 'extrusion' => array(), - ) + [ + 'frame' => ['width' => 100, 'height' => 70, 'left' => 1, 'top' => 1], + 'fill' => ['color' => '#33CC99'], + 'outline' => ['color' => '#333333', 'weight' => 2], + 'extrusion' => [], + ] ); // Save file diff --git a/samples/Sample_32_Chart.php b/samples/Sample_32_Chart.php index b61a646fde..c8c65e989e 100644 --- a/samples/Sample_32_Chart.php +++ b/samples/Sample_32_Chart.php @@ -1,32 +1,44 @@ addTitleStyle(1, array('size' => 14, 'bold' => true), array('keepNext' => true, 'spaceBefore' => 240)); -$phpWord->addTitleStyle(2, array('size' => 14, 'bold' => true), array('keepNext' => true, 'spaceBefore' => 240)); +// Define styles +$phpWord->addTitleStyle(1, ['size' => 14, 'bold' => true], ['keepNext' => true, 'spaceBefore' => 240]); +$phpWord->addTitleStyle(2, ['size' => 14, 'bold' => true], ['keepNext' => true, 'spaceBefore' => 240]); // 2D charts $section = $phpWord->addSection(); -$section->addTitle(htmlspecialchars('2D charts'), 1); -$section = $phpWord->addSection(array('colsNum' => 2, 'breakType' => 'continuous')); +$section->addTitle('2D charts', 1); +$section = $phpWord->addSection(['colsNum' => 2, 'breakType' => 'continuous']); -$chartTypes = array('pie', 'doughnut', 'bar', 'column', 'line', 'area', 'scatter', 'radar'); -$twoSeries = array('bar', 'column', 'line', 'area', 'scatter', 'radar'); -$threeSeries = array('bar', 'line'); -$categories = array('A', 'B', 'C', 'D', 'E'); -$series1 = array(1, 3, 2, 5, 4); -$series2 = array(3, 1, 7, 2, 6); -$series3 = array(8, 3, 2, 5, 4); +$chartTypes = ['pie', 'doughnut', 'bar', 'column', 'line', 'area', 'scatter', 'radar', 'stacked_bar', 'percent_stacked_bar', 'stacked_column', 'percent_stacked_column']; +$twoSeries = ['bar', 'column', 'line', 'area', 'scatter', 'radar', 'stacked_bar', 'percent_stacked_bar', 'stacked_column', 'percent_stacked_column']; +$threeSeries = ['bar', 'line']; +$categories = ['A', 'B', 'C', 'D', 'E']; +$series1 = [1, 3, 2, 5, 4]; +$series2 = [3, 1, 7, 2, 6]; +$series3 = [8, 3, 2, 5, 4]; +$showGridLines = false; +$showAxisLabels = false; +$showLegend = true; +$legendPosition = 't'; +// r = right, l = left, t = top, b = bottom, tr = top right foreach ($chartTypes as $chartType) { $section->addTitle(ucfirst($chartType), 2); $chart = $section->addChart($chartType, $categories, $series1); $chart->getStyle()->setWidth(Converter::inchToEmu(2.5))->setHeight(Converter::inchToEmu(2)); + $chart->getStyle()->setShowGridX($showGridLines); + $chart->getStyle()->setShowGridY($showGridLines); + $chart->getStyle()->setShowAxisLabels($showAxisLabels); + $chart->getStyle()->setShowLegend($showLegend); + $chart->getStyle()->setLegendPosition($legendPosition); if (in_array($chartType, $twoSeries)) { $chart->addSeries($categories, $series2); } @@ -37,13 +49,20 @@ } // 3D charts -$section = $phpWord->addSection(array('breakType' => 'continuous')); -$section->addTitle(htmlspecialchars('3D charts'), 1); -$section = $phpWord->addSection(array('colsNum' => 2, 'breakType' => 'continuous')); +$section = $phpWord->addSection(['breakType' => 'continuous']); +$section->addTitle('3D charts', 1); +$section = $phpWord->addSection(['colsNum' => 2, 'breakType' => 'continuous']); -$chartTypes = array('pie', 'bar', 'column', 'line', 'area'); -$multiSeries = array('bar', 'column', 'line', 'area'); -$style = array('width' => Converter::cmToEmu(5), 'height' => Converter::cmToEmu(4), '3d' => true); +$chartTypes = ['pie', 'bar', 'column', 'line', 'area']; +$multiSeries = ['bar', 'column', 'line', 'area']; +$style = [ + 'width' => Converter::cmToEmu(5), + 'height' => Converter::cmToEmu(4), + '3d' => true, + 'showAxisLabels' => $showAxisLabels, + 'showGridX' => $showGridLines, + 'showGridY' => $showGridLines, +]; foreach ($chartTypes as $chartType) { $section->addTitle(ucfirst($chartType), 2); $chart = $section->addChart($chartType, $categories, $series1, $style); diff --git a/samples/Sample_33_FormField.php b/samples/Sample_33_FormField.php index 9f356810dc..223fdd2987 100644 --- a/samples/Sample_33_FormField.php +++ b/samples/Sample_33_FormField.php @@ -1,23 +1,25 @@ getProtection()->setEditing('forms'); +$phpWord = new PhpOffice\PhpWord\PhpWord(); +$phpWord->getSettings()->getDocumentProtection()->setEditing('forms'); +// New section $section = $phpWord->addSection(); $textrun = $section->addTextRun(); -$textrun->addText(htmlspecialchars('Form fields can be added in a text run and can be in form of textinput ')); +$textrun->addText('Form fields can be added in a text run and can be in form of textinput '); $textrun->addFormField('textinput')->setName('MyTextBox'); -$textrun->addText(htmlspecialchars(', checkbox ')); +$textrun->addText(', checkbox '); $textrun->addFormField('checkbox')->setDefault(true); -$textrun->addText(htmlspecialchars(', or dropdown ')); -$textrun->addFormField('dropdown')->setEntries(array('Choice 1', 'Choice 2', 'Choice 3')); -$textrun->addText(htmlspecialchars('. You have to set document protection to "forms" to enable dropdown.')); +$textrun->addText(', or dropdown '); +$textrun->addFormField('dropdown')->setEntries(['Choice 1', 'Choice 2', 'Choice 3']); +$textrun->addText('. You have to set document protection to "forms" to enable dropdown.'); -$section->addText(htmlspecialchars('They can also be added as a stand alone paragraph.')); +$section->addText('They can also be added as a stand alone paragraph.'); $section->addFormField('textinput')->setValue('Your name'); // Save file diff --git a/samples/Sample_34_SDT.php b/samples/Sample_34_SDT.php index 19ac42fe34..4c8f588e81 100644 --- a/samples/Sample_34_SDT.php +++ b/samples/Sample_34_SDT.php @@ -1,23 +1,31 @@ addSection(); $textrun = $section->addTextRun(); -$textrun->addText(htmlspecialchars('Combobox: ')); -$textrun->addSDT('comboBox')->setListItems(array('1' => 'Choice 1', '2' => 'Choice 2')); +$textrun->addText('Combobox: '); +$textrun->addSDT('comboBox')->setListItems(['1' => 'Choice 1', '2' => 'Choice 2']); $textrun = $section->addTextRun(); -$textrun->addText(htmlspecialchars('Date: ')); +$textrun->addText('Date: '); $textrun->addSDT('date'); +$textrun->addTextBreak(1); +$textrun->addText('Date with pre set value: '); +$textrun->addSDT('date')->setValue('03/30/2017'); +$textrun->addTextBreak(1); +$textrun->addText('Date with pre set value: '); +$textrun->addSDT('date')->setValue('30.03.2017'); $textrun = $section->addTextRun(); -$textrun->addText(htmlspecialchars('Drop down list: ')); -$textrun->addSDT('dropDownList')->setListItems(array('1' => 'Choice 1', '2' => 'Choice 2')); +$textrun->addText('Drop down list: '); +$textrun->addSDT('dropDownList')->setListItems(['1' => 'Choice 1', '2' => 'Choice 2'])->setValue('Choice 1'); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_35_InternalLink.php b/samples/Sample_35_InternalLink.php index 1b907f8724..ef10c4b61f 100644 --- a/samples/Sample_35_InternalLink.php +++ b/samples/Sample_35_InternalLink.php @@ -1,18 +1,19 @@ addSection(); -$section->addTitle(htmlspecialchars('This is page 1'), 1); +$section->addTitle('This is page 1', 1); $linkIsInternal = true; -$section->addLink('MyBookmark', htmlspecialchars('Take me to page 3'), null, null, $linkIsInternal); +$section->addLink('MyBookmark', 'Take me to page 3', null, null, $linkIsInternal); $section->addPageBreak(); -$section->addTitle(htmlspecialchars('This is page 2'), 1); +$section->addTitle('This is page 2', 1); $section->addPageBreak(); -$section->addTitle(htmlspecialchars('This is page 3'), 1); +$section->addTitle('This is page 3', 1); $section->addBookmark('MyBookmark'); // Save file diff --git a/samples/Sample_36_RTL.php b/samples/Sample_36_RTL.php index ec326ae025..e3cf84576a 100644 --- a/samples/Sample_36_RTL.php +++ b/samples/Sample_36_RTL.php @@ -1,16 +1,48 @@ 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(); + $textrun = $section->addTextRun(); -$textrun->addText(htmlspecialchars('This is a Left to Right paragraph.')); +$textrun->addText('This is a Left to Right paragraph.'); + +$textrun = $section->addTextRun(['alignment' => PhpOffice\PhpWord\SimpleType\Jc::END]); +$textrun->addText('سلام این یک پاراگراف راست به چپ است', ['rtl' => true]); + +$section->addText('Table visually presented as RTL'); +$style = ['rtl' => true, 'size' => 12]; +$tableStyle = ['borderSize' => 6, 'borderColor' => '000000', 'width' => 5000, 'unit' => PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT, 'bidiVisual' => true]; + +$table = $section->addTable($tableStyle); +$cellHCentered = ['alignment' => PhpOffice\PhpWord\SimpleType\Jc::CENTER]; +$cellHEnd = ['alignment' => PhpOffice\PhpWord\SimpleType\Jc::END]; +$cellVCentered = ['valign' => PhpOffice\PhpWord\SimpleType\VerticalJc::CENTER]; + +//Vidually bidirectinal table +$table->addRow(); +$cell = $table->addCell(1500, $cellVCentered); +$textrun = $cell->addTextRun($cellHCentered); +$textrun->addText('ردیف', $style); + +$cell = $table->addCell(2000); +$textrun = $cell->addTextRun($cellHEnd); +$textrun->addText('سوالات', $style); -$textrun = $section->addTextRun(array('align' => 'right')); -$textrun->addText(htmlspecialchars('سلام این یک پاراگراف راست به چپ است'), array('rtl' => true)); +$cell = $table->addCell(1000, $cellVCentered); +$textrun = $cell->addTextRun($cellHCentered); +$textrun->addText('بارم', $style); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_37_Comments.php b/samples/Sample_37_Comments.php new file mode 100644 index 0000000000..ac03a699e4 --- /dev/null +++ b/samples/Sample_37_Comments.php @@ -0,0 +1,78 @@ +addText('Test', ['bold' => true]); +$phpWord->addComment($comment); + +$section = $phpWord->addSection(); + +$textrun = $section->addTextRun(); +$textrun->addText('This '); +$text = $textrun->addText('is'); +$text->setCommentRangeStart($comment); +$textrun->addText(' a test'); + +$section->addTextBreak(2); + +// Let's create a comment that we will link to a start element and an end element +$commentWithStartAndEnd = new PhpOffice\PhpWord\Element\Comment('Foo Bar', new DateTime()); +$commentWithStartAndEnd->addText('A comment with a start and an end'); +$phpWord->addComment($commentWithStartAndEnd); + +$textrunWithEnd = $section->addTextRun(); +$textrunWithEnd->addText('This '); +$textToStartOn = $textrunWithEnd->addText('is', ['bold' => true]); +$textToStartOn->setCommentRangeStart($commentWithStartAndEnd); +$textrunWithEnd->addText(' another', ['italic' => true]); +$textToEndOn = $textrunWithEnd->addText(' test'); +$textToEndOn->setCommentRangeEnd($commentWithStartAndEnd); + +$section->addTextBreak(2); + +// Let's add a comment on an image +$commentOnImage = new PhpOffice\PhpWord\Element\Comment('Mr Smart', new DateTime()); +$imageComment = $commentOnImage->addTextRun(); +$imageComment->addText('Hey, Mars does look '); +$imageComment->addText('red', ['color' => 'FF0000']); +$phpWord->addComment($commentOnImage); +$image = $section->addImage(__DIR__ . '/resources/_mars.jpg'); +$image->setCommentRangeStart($commentOnImage); + +$section->addTextBreak(2); + +// We can also do things the other way round, link the comment to the element +$anotherText = $section->addText('another text'); + +$comment1 = new PhpOffice\PhpWord\Element\Comment('Authors name', new DateTime(), 'my_initials'); +$comment1->addText('Test', ['bold' => true]); +$comment1->setStartElement($anotherText); +$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) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/Sample_38_Protection.php b/samples/Sample_38_Protection.php new file mode 100644 index 0000000000..737946d51a --- /dev/null +++ b/samples/Sample_38_Protection.php @@ -0,0 +1,22 @@ +getSettings()->getDocumentProtection(); +$documentProtection->setEditing(DocProtect::READ_ONLY); +$documentProtection->setPassword('myPassword'); + +$section = $phpWord->addSection(); +$section->addText('this document is password protected'); + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/Sample_39_TrackChanges.php b/samples/Sample_39_TrackChanges.php new file mode 100644 index 0000000000..a2c2bcf61c --- /dev/null +++ b/samples/Sample_39_TrackChanges.php @@ -0,0 +1,30 @@ +addSection(); +$textRun = $section->addTextRun(); + +$text = $textRun->addText('Hello World! Time to '); + +$text = $textRun->addText('wake ', ['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))); + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/Sample_40_TemplateSetComplexValue.php b/samples/Sample_40_TemplateSetComplexValue.php new file mode 100644 index 0000000000..41cbab8950 --- /dev/null +++ b/samples/Sample_40_TemplateSetComplexValue.php @@ -0,0 +1,46 @@ +addText('This title has been set ', ['bold' => true, 'italic' => true, 'color' => 'blue']); +$title->addText('dynamically', ['bold' => true, 'italic' => true, 'color' => 'red', 'underline' => 'single']); +$templateProcessor->setComplexBlock('title', $title); + +$inline = new TextRun(); +$inline->addText('by a red italic text', ['italic' => true, 'color' => 'red']); +$templateProcessor->setComplexValue('inline', $inline); + +$table = new Table(['borderSize' => 12, 'borderColor' => 'green', 'width' => 6000, 'unit' => TblWidth::TWIP]); +$table->addRow(); +$table->addCell(150)->addText('Cell A1'); +$table->addCell(150)->addText('Cell A2'); +$table->addCell(150)->addText('Cell A3'); +$table->addRow(); +$table->addCell(150)->addText('Cell B1'); +$table->addCell(150)->addText('Cell B2'); +$table->addCell(150)->addText('Cell B3'); +$templateProcessor->setComplexBlock('table', $table); + +$field = new Field('DATE', ['dateformat' => 'dddd d MMMM yyyy H:mm:ss'], ['PreserveFormat']); +$templateProcessor->setComplexValue('field', $field); + +// $link = new Link('https://github.com/PHPOffice/PHPWord'); +// $templateProcessor->setComplexValue('link', $link); + +echo date('H:i:s'), ' Saving the result document...', EOL; +$templateProcessor->saveAs(__DIR__ . '/results/Sample_40_TemplateSetComplexValue.docx'); + +echo getEndingNotes(['Word2007' => 'docx'], __DIR__ . '/results/Sample_40_TemplateSetComplexValue.docx'); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/Sample_41_TemplateSetChart.php b/samples/Sample_41_TemplateSetChart.php new file mode 100644 index 0000000000..36be5888f7 --- /dev/null +++ b/samples/Sample_41_TemplateSetChart.php @@ -0,0 +1,46 @@ +addSeries($categories, $series2); + } + if (in_array($chartType, $threeSeries)) { + $chart->addSeries($categories, $series3); + } + + $chart->getStyle() + ->setWidth(Converter::inchToEmu(3)) + ->setHeight(Converter::inchToEmu(3)); + + $templateProcessor->setChart("chart{$i}", $chart); + ++$i; +} + +echo date('H:i:s'), ' Saving the result document...', EOL; +$templateProcessor->saveAs(__DIR__ . '/results/Sample_41_TemplateSetChart.docx'); + +echo getEndingNotes(['Word2007' => 'docx'], __DIR__ . '/results/Sample_41_TemplateSetChart.docx'); +if (!CLI) { + include_once 'Sample_Footer.php'; +} 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..8c1703499f --- /dev/null +++ b/samples/Sample_44_ExtractVariablesFromReaderWord2007.php @@ -0,0 +1,14 @@ +getSettings()->setThemeFontLang($languageEnGb); + +$fontStyleName = 'rStyle'; +$phpWord->addFontStyle($fontStyleName, ['bold' => true, 'italic' => true, 'size' => 16, 'allCaps' => true, 'doubleStrikethrough' => true]); + +$paragraphStyleName = 'pStyle'; +$phpWord->addParagraphStyle($paragraphStyleName, ['alignment' => PhpOffice\PhpWord\SimpleType\Jc::CENTER, 'spaceAfter' => 100]); + +$phpWord->addTitleStyle(1, ['bold' => true], ['spaceAfter' => 240]); + +// New portrait section +$section = $phpWord->addSection(); + +// Simple text +$section->addTitle('Welcome to PhpWord', 1); +$section->addText('Hello World!'); + +// $pStyle = new Font(); +// $pStyle->setLang() +$section->addText('Ce texte-ci est en français.', ['lang' => PhpOffice\PhpWord\Style\Language::FR_BE]); + +// Two text break +$section->addTextBreak(2); + +// Define styles +$section->addText('I am styled by a font style definition.', $fontStyleName); +$section->addText('I am styled by a paragraph style definition.', null, $paragraphStyleName); +$section->addText('I am styled by both font and paragraph style.', $fontStyleName, $paragraphStyleName); + +$section->addTextBreak(); + +// Inline font style +$fontStyle['name'] = 'Times New Roman'; +$fontStyle['size'] = 20; + +$textrun = $section->addTextRun(); +$textrun->addText('I am inline styled ', $fontStyle); +$textrun->addText('with '); +$textrun->addText('color', ['color' => '996699']); +$textrun->addText(', '); +$textrun->addText('bold', ['bold' => true]); +$textrun->addText(', '); +$textrun->addText('italic', ['italic' => true]); +$textrun->addText(', '); +$textrun->addText('underline', ['underline' => 'dash']); +$textrun->addText(', '); +$textrun->addText('strikethrough', ['strikethrough' => true]); +$textrun->addText(', '); +$textrun->addText('doubleStrikethrough', ['doubleStrikethrough' => true]); +$textrun->addText(', '); +$textrun->addText('superScript', ['superScript' => true]); +$textrun->addText(', '); +$textrun->addText('subScript', ['subScript' => true]); +$textrun->addText(', '); +$textrun->addText('smallCaps', ['smallCaps' => true]); +$textrun->addText(', '); +$textrun->addText('allCaps', ['allCaps' => true]); +$textrun->addText(', '); +$textrun->addText('fgColor', ['fgColor' => 'yellow']); +$textrun->addText(', '); +$textrun->addText('scale', ['scale' => 200]); +$textrun->addText(', '); +$textrun->addText('spacing', ['spacing' => 120]); +$textrun->addText(', '); +$textrun->addText('kerning', ['kerning' => 10]); +$textrun->addText('. '); + +// Link +$section->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord on GitHub'); +$section->addTextBreak(); + +// Image +$section->addImage(__DIR__ . '/resources/_earth.jpg', ['width' => 18, 'height' => 18]); + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/Sample_46_RubyPhoneticGuide.php b/samples/Sample_46_RubyPhoneticGuide.php new file mode 100644 index 0000000000..0d991de756 --- /dev/null +++ b/samples/Sample_46_RubyPhoneticGuide.php @@ -0,0 +1,70 @@ +addSection(); + +$section->addText('Here is some normal text with no ruby, also known as "phonetic guide", text.'); + +$properties = new RubyProperties(); +$properties->setAlignment(RubyProperties::ALIGNMENT_CENTER); +$properties->setFontFaceSize(10); +$properties->setFontPointsAboveBaseText(20); +$properties->setFontSizeForBaseText(18); +$properties->setLanguageId('en-US'); + +$textRun = $section->addTextRun(); +$textRun->addText('Here is a demonstration of ruby text for '); +$baseTextRun = new TextRun(null); +$baseTextRun->addText('this'); +$rubyTextRun = new TextRun(null); +$rubyTextRun->addText('ruby-text'); +$textRun->addRuby($baseTextRun, $rubyTextRun, $properties); +$textRun->addText(' word.'); + +$textRun = $section->addTextRun(); +$properties = new RubyProperties(); +$properties->setAlignment(RubyProperties::ALIGNMENT_CENTER); +$properties->setFontFaceSize(10); +$properties->setFontPointsAboveBaseText(20); +$properties->setFontSizeForBaseText(18); +$properties->setLanguageId('ja-JP'); +$textRun->addText('Here is a demonstration of ruby text for Japanese text: '); +$baseTextRun = new TextRun(null); +$baseTextRun->addText('私'); +$rubyTextRun = new TextRun(null); +$rubyTextRun->addText('わたし'); +$textRun->addRuby($baseTextRun, $rubyTextRun, $properties); + +$section->addText('You can also have ruby text for titles:'); + +$phpWord->addTitleStyle(1, ['name' => 'Arial', 'size' => 24, 'bold' => true, 'color' => '000099']); + +$properties = new RubyProperties(); +$properties->setAlignment(RubyProperties::ALIGNMENT_CENTER); +$properties->setFontFaceSize(10); +$properties->setFontPointsAboveBaseText(50); +$properties->setFontSizeForBaseText(18); +$properties->setLanguageId('ja-JP'); + +$baseTextRun = new TextRun(null); +$baseTextRun->addText('私'); +$rubyTextRun = new TextRun(null); +$rubyTextRun->addText('わたし'); +$textRun = new TextRun(); +$textRun->addRuby($baseTextRun, $rubyTextRun, $properties); +$section->addTitle($textRun, 1); + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/Sample_Footer.php b/samples/Sample_Footer.php index 95ac693fc3..2d89bfd253 100644 --- a/samples/Sample_Footer.php +++ b/samples/Sample_Footer.php @@ -1,7 +1,4 @@ '); define('SCRIPT_FILENAME', basename($_SERVER['SCRIPT_FILENAME'], '.php')); define('IS_INDEX', SCRIPT_FILENAME == 'index'); -Autoloader::register(); Settings::loadConfig(); +if (defined('DOMPDF_ENABLE_AUTOLOAD')) { + Settings::setPdfRenderer(Settings::PDF_RENDERER_DOMPDF, $vendorDirPath . '/dompdf/dompdf'); +} + // Set writers -$writers = array('Word2007' => 'docx', 'ODText' => 'odt', 'RTF' => 'rtf', 'HTML' => 'html', 'PDF' => 'pdf'); +$writers = ['Word2007' => 'docx', 'ODText' => 'odt', 'RTF' => 'rtf', 'HTML' => 'html', 'PDF' => 'pdf', 'EPub3' => 'epub']; // Set PDF renderer if (null === Settings::getPdfRendererPath()) { $writers['PDF'] = null; } +// Turn output escaping on +Settings::setOutputEscapingEnabled(true); + // Return to the caller script when runs by CLI if (CLI) { return; @@ -40,25 +55,25 @@ // Populate samples $files = ''; if ($handle = opendir('.')) { - while (false !== ($file = readdir($handle))) { + $sampleFiles = []; + while (false !== ($sampleFile = readdir($handle))) { + $sampleFiles[] = $sampleFile; + } + sort($sampleFiles); + closedir($handle); + + foreach ($sampleFiles as $file) { if (preg_match('/^Sample_\d+_/', $file)) { $name = str_replace('_', ' ', preg_replace('/(Sample_|\.php)/', '', $file)); $files .= "
  • {$name}
  • "; } } - closedir($handle); } /** - * Write documents - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord - * @param string $filename - * @param array $writers - * - * @return string + * Write documents. */ -function write($phpWord, $filename, $writers) +function write(PhpOffice\PhpWord\PhpWord $phpWord, string $filename, array $writers): string { $result = ''; @@ -74,26 +89,22 @@ function write($phpWord, $filename, $writers) $result .= EOL; } - $result .= getEndingNotes($writers); + $result .= getEndingNotes($writers, $filename); return $result; } /** - * Get ending notes - * - * @param array $writers - * - * @return string + * Get ending notes. */ -function getEndingNotes($writers) +function getEndingNotes(array $writers, string $filename): string { $result = ''; // Do not show execution time for index if (!IS_INDEX) { - $result .= date('H:i:s') . " Done writing file(s)" . EOL; - $result .= date('H:i:s') . " Peak memory usage: " . (memory_get_peak_usage(true) / 1024 / 1024) . " MB" . EOL; + $result .= date('H:i:s') . ' Done writing file(s)' . EOL; + $result .= date('H:i:s') . ' Peak memory usage: ' . (memory_get_peak_usage(true) / 1024 / 1024) . ' MB' . EOL; } // Return @@ -105,7 +116,7 @@ function getEndingNotes($writers) $result .= '

     

    '; $result .= '

    Results: '; foreach ($types as $type) { - if (!is_null($type)) { + if (null !== $type) { $resultFile = 'results/' . SCRIPT_FILENAME . '.' . $type; if (file_exists($resultFile)) { $result .= "{$type} "; @@ -113,6 +124,12 @@ function getEndingNotes($writers) } } $result .= '

    '; + + $result .= '
    ';
    +            if (file_exists($filename . '.php')) {
    +                $result .= highlight_file($filename . '.php', true);
    +            }
    +            $result .= '
    '; } } @@ -149,8 +166,8 @@ function getEndingNotes($writers) diff --git a/samples/index.php b/samples/index.php index a65d8fd90e..a9733d2c7c 100644 --- a/samples/index.php +++ b/samples/index.php @@ -3,41 +3,41 @@ use PhpOffice\PhpWord\Settings; -$requirements = array( - 'php' => array('PHP 5.3.3', version_compare(PHP_VERSION, '5.3.3', '>=')), - 'xml' => array('PHP extension XML', extension_loaded('xml')), - 'temp' => array('Temp folder "' . Settings::getTempDir() . '" is writable', is_writable(Settings::getTempDir())), - 'zip' => array('PHP extension ZipArchive (optional)', extension_loaded('zip')), - 'gd' => array('PHP extension GD (optional)', extension_loaded('gd')), - 'xmlw' => array('PHP extension XMLWriter (optional)', extension_loaded('xmlwriter')), - 'xsl' => array('PHP extension XSL (optional)', extension_loaded('xsl')), -); +$requirements = [ + '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')], + 'gd' => ['PHP extension GD (optional)', extension_loaded('gd')], + 'xmlw' => ['PHP extension XMLWriter (optional)', extension_loaded('xmlwriter')], + 'xsl' => ['PHP extension XSL (optional)', extension_loaded('xsl')], +]; if (!CLI) { -?> + ?>

    Welcome to PHPWord, a library written in pure PHP that provides a set of classes to write to and read from different document file formats, i.e. Office Open XML (.docx), Open Document Format (.odt), and Rich Text Format (.rtf).

     

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

    -Requirement check:"; - echo "
      "; + echo '

      Requirement check:

      '; + echo '
        '; foreach ($requirements as $key => $value) { - list($label, $result) = $value; + [$label, $result] = $value; $status = $result ? 'passed' : 'failed'; echo "
      • {$label} ... {$status}
      • "; } - echo "
      "; + echo '
    '; include_once 'Sample_Footer.php'; } else { echo 'Requirement check:' . PHP_EOL; foreach ($requirements as $key => $value) { - list($label, $result) = $value; + [$label, $result] = $value; $label = strip_tags($label); $status = $result ? '32m passed' : '31m failed'; echo "{$label} ... \033[{$status}\033[0m" . PHP_EOL; diff --git a/samples/resources/Sample_11_ReadWord2007.docx b/samples/resources/Sample_11_ReadWord2007.docx index c9a50f485a..f6526360fd 100644 Binary files a/samples/resources/Sample_11_ReadWord2007.docx and b/samples/resources/Sample_11_ReadWord2007.docx differ diff --git a/samples/resources/Sample_24_ReadODText.odt b/samples/resources/Sample_24_ReadODText.odt index d37c4e6629..59ac16da03 100644 Binary files a/samples/resources/Sample_24_ReadODText.odt and b/samples/resources/Sample_24_ReadODText.odt differ diff --git a/samples/resources/Sample_28_ReadRTF.rtf b/samples/resources/Sample_28_ReadRTF.rtf index 6f9ac0f8b8..ccccc0e707 100644 --- a/samples/resources/Sample_28_ReadRTF.rtf +++ b/samples/resources/Sample_28_ReadRTF.rtf @@ -16,6 +16,6 @@ \pard\nowidctlpar\qc\sa100{\cf0\f0\fs32\b\i I am styled by both font and paragraph style.}\par \pard\nowidctlpar{\cf1\f1\fs40\b\i\ul\strike\super I am inline styled.}\par \par -{\field {\*\fldinst {HYPERLINK "http://www.google.com/"}}{\fldrslt {Google}}}\par +{\field {\*\fldinst {HYPERLINK "https://github.com/PHPOffice/PHPWord"}}{\fldrslt {PHPWord on GitHub}}}\par \par } \ No newline at end of file diff --git a/samples/resources/Sample_30_ReadHTML.html b/samples/resources/Sample_30_ReadHTML.html index 5593298bfb..f9ce478c20 100644 --- a/samples/resources/Sample_30_ReadHTML.html +++ b/samples/resources/Sample_30_ReadHTML.html @@ -11,5 +11,15 @@

    Adding element via HTML

    • Item 1
    • Item 2
      • Item 2.1
      • Item 2.1

    Ordered (numbered) list:

    1. Item 1
    2. Item 2
    + +

    Double height

    + +

    Includes images

    + + + + + + diff --git a/samples/resources/Sample_40_TemplateSetComplexValue.docx b/samples/resources/Sample_40_TemplateSetComplexValue.docx new file mode 100644 index 0000000000..7265908e8c Binary files /dev/null and b/samples/resources/Sample_40_TemplateSetComplexValue.docx differ diff --git a/samples/resources/Sample_41_TemplateSetChart.docx b/samples/resources/Sample_41_TemplateSetChart.docx new file mode 100644 index 0000000000..c958b335c8 Binary files /dev/null and b/samples/resources/Sample_41_TemplateSetChart.docx differ 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/samples/results/.gitignore b/samples/results/.gitignore old mode 100644 new mode 100755 diff --git a/src/PhpWord/Autoloader.php b/src/PhpWord/Autoloader.php index 68da845de3..122d9c05fd 100644 --- a/src/PhpWord/Autoloader.php +++ b/src/PhpWord/Autoloader.php @@ -1,4 +1,5 @@ items; } /** - * Get item by index + * Get item by index. * - * @param int $index - * @return mixed + * @return ?T */ - public function getItem($index) + public function getItem(int $index) { if (array_key_exists($index, $this->items)) { return $this->items[$index]; - } else { - return null; } + + return null; } /** * Set item. * - * @param int $index - * @param mixed $item - * @return void + * @param ?T $item */ - public function setItem($index, $item) + public function setItem(int $index, $item): void { if (array_key_exists($index, $this->items)) { $this->items[$index] = $item; @@ -71,25 +71,22 @@ public function setItem($index, $item) } /** - * Add new item + * Add new item. * - * @param mixed $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; } /** - * Get item count - * - * @return int + * Get item count. */ - 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 cb9d74d53f..0694bf7dd8 100644 --- a/src/PhpWord/Collection/Bookmarks.php +++ b/src/PhpWord/Collection/Bookmarks.php @@ -1,4 +1,5 @@ */ class Bookmarks extends AbstractCollection { diff --git a/src/PhpWord/Collection/Charts.php b/src/PhpWord/Collection/Charts.php index cfccee2ec3..ef1c6597ff 100644 --- a/src/PhpWord/Collection/Charts.php +++ b/src/PhpWord/Collection/Charts.php @@ -1,4 +1,5 @@ */ class Charts extends AbstractCollection { diff --git a/src/PhpWord/Template.php b/src/PhpWord/Collection/Comments.php similarity index 68% rename from src/PhpWord/Template.php rename to src/PhpWord/Collection/Comments.php index f6ad790eab..4f1394359f 100644 --- a/src/PhpWord/Template.php +++ b/src/PhpWord/Collection/Comments.php @@ -1,4 +1,5 @@ */ -class Template extends TemplateProcessor +class Comments extends AbstractCollection { } diff --git a/src/PhpWord/Collection/Endnotes.php b/src/PhpWord/Collection/Endnotes.php index c16486ae65..3de1ad22d5 100644 --- a/src/PhpWord/Collection/Endnotes.php +++ b/src/PhpWord/Collection/Endnotes.php @@ -1,4 +1,5 @@ */ class Endnotes extends AbstractCollection { diff --git a/src/PhpWord/Collection/Footnotes.php b/src/PhpWord/Collection/Footnotes.php index e83ac873a7..804a45c873 100644 --- a/src/PhpWord/Collection/Footnotes.php +++ b/src/PhpWord/Collection/Footnotes.php @@ -1,4 +1,5 @@ */ class Footnotes extends AbstractCollection { diff --git a/src/PhpWord/Collection/Titles.php b/src/PhpWord/Collection/Titles.php index 7e5e5da787..1a943697d2 100644 --- a/src/PhpWord/Collection/Titles.php +++ b/src/PhpWord/Collection/Titles.php @@ -1,4 +1,5 @@ */ class Titles extends AbstractCollection { diff --git a/src/PhpWord/ComplexType/FootnoteProperties.php b/src/PhpWord/ComplexType/FootnoteProperties.php new file mode 100644 index 0000000000..10c426e84c --- /dev/null +++ b/src/PhpWord/ComplexType/FootnoteProperties.php @@ -0,0 +1,185 @@ +pos; + } + + /** + * Set the Footnote Positioning Location (pageBottom, beneathText, sectEnd, docEnd). + * + * @param string $pos + * + * @return self + */ + public function setPos($pos) + { + $position = [ + self::POSITION_PAGE_BOTTOM, + self::POSITION_BENEATH_TEXT, + self::POSITION_SECTION_END, + self::POSITION_DOC_END, + ]; + + if (in_array($pos, $position)) { + $this->pos = $pos; + } else { + throw new InvalidArgumentException('Invalid value, on of ' . implode(', ', $position) . ' possible'); + } + + return $this; + } + + /** + * Get the Footnote Numbering Format. + * + * @return string + */ + public function getNumFmt() + { + return $this->numFmt; + } + + /** + * Set the Footnote Numbering Format. + * + * @param string $numFmt One of NumberFormat + * + * @return self + */ + public function setNumFmt($numFmt) + { + NumberFormat::validate($numFmt); + $this->numFmt = $numFmt; + + return $this; + } + + /** + * Get the Footnote Numbering Format. + * + * @return float + */ + public function getNumStart() + { + return $this->numStart; + } + + /** + * Set the Footnote Numbering Format. + * + * @param float $numStart + * + * @return self + */ + public function setNumStart($numStart) + { + $this->numStart = $numStart; + + return $this; + } + + /** + * Get the Footnote and Endnote Numbering Starting Value. + * + * @return string + */ + public function getNumRestart() + { + return $this->numRestart; + } + + /** + * Set the Footnote and Endnote Numbering Starting Value (continuous, eachSect, eachPage). + * + * @param string $numRestart + * + * @return self + */ + public function setNumRestart($numRestart) + { + $restartNumbers = [ + self::RESTART_NUMBER_CONTINUOUS, + self::RESTART_NUMBER_EACH_SECTION, + self::RESTART_NUMBER_EACH_PAGE, + ]; + + if (in_array($numRestart, $restartNumbers)) { + $this->numRestart = $numRestart; + } else { + throw new InvalidArgumentException('Invalid value, on of ' . implode(', ', $restartNumbers) . ' possible'); + } + + return $this; + } +} diff --git a/src/PhpWord/ComplexType/ProofState.php b/src/PhpWord/ComplexType/ProofState.php new file mode 100644 index 0000000000..0731238201 --- /dev/null +++ b/src/PhpWord/ComplexType/ProofState.php @@ -0,0 +1,109 @@ +spelling = $spelling; + } else { + throw new InvalidArgumentException('Invalid value, dirty or clean possible'); + } + + return $this; + } + + /** + * Get the Spell Checking State. + * + * @return string + */ + public function getSpelling() + { + return $this->spelling; + } + + /** + * Set the Grammatical Checking State (dirty or clean). + * + * @param string $grammar + * + * @return self + */ + public function setGrammar($grammar) + { + if ($grammar == self::CLEAN || $grammar == self::DIRTY) { + $this->grammar = $grammar; + } else { + throw new InvalidArgumentException('Invalid value, dirty or clean possible'); + } + + return $this; + } + + /** + * Get the Grammatical Checking State. + * + * @return string + */ + public function getGrammar() + { + return $this->grammar; + } +} diff --git a/src/PhpWord/ComplexType/RubyProperties.php b/src/PhpWord/ComplexType/RubyProperties.php new file mode 100644 index 0000000000..2409151644 --- /dev/null +++ b/src/PhpWord/ComplexType/RubyProperties.php @@ -0,0 +1,188 @@ +alignment = self::ALIGNMENT_DISTRIBUTE_SPACE; + $this->fontFaceSize = 12; + $this->fontPointsAboveText = 22; + $this->languageId = 'ja-JP'; + $this->baseTextFontSize = 24; + } + + /** + * Get the ruby alignment. + */ + public function getAlignment(): string + { + return $this->alignment; + } + + /** + * Set the Ruby Alignment (center, distributeLetter, distributeSpace, left, right, rightVertical). + */ + public function setAlignment(string $alignment): self + { + $alignmentTypes = [ + self::ALIGNMENT_CENTER, + self::ALIGNMENT_DISTRIBUTE_LETTER, + self::ALIGNMENT_DISTRIBUTE_SPACE, + self::ALIGNMENT_LEFT, + self::ALIGNMENT_RIGHT, + self::ALIGNMENT_RIGHT_VERTICAL, + ]; + + if (in_array($alignment, $alignmentTypes)) { + $this->alignment = $alignment; + } else { + throw new InvalidArgumentException('Invalid value, alignments of ' . implode(', ', $alignmentTypes) . ' possible'); + } + + return $this; + } + + /** + * Get the ruby font face size. + */ + public function getFontFaceSize(): float + { + return $this->fontFaceSize; + } + + /** + * Set the ruby font face size. + */ + public function setFontFaceSize(float $size): self + { + $this->fontFaceSize = $size; + + return $this; + } + + /** + * Get the ruby font points above base text. + */ + public function getFontPointsAboveBaseText(): float + { + return $this->fontPointsAboveText; + } + + /** + * Set the ruby font points above base text. + */ + public function setFontPointsAboveBaseText(float $size): self + { + $this->fontPointsAboveText = $size; + + return $this; + } + + /** + * Get the ruby font size for base text. + */ + public function getFontSizeForBaseText(): float + { + return $this->baseTextFontSize; + } + + /** + * Set the ruby font size for base text. + */ + public function setFontSizeForBaseText(float $size): self + { + $this->baseTextFontSize = $size; + + return $this; + } + + /** + * Get the ruby language id. + */ + public function getLanguageId(): string + { + return $this->languageId; + } + + /** + * Set the ruby language id. + */ + public function setLanguageId(string $langId): self + { + $this->languageId = $langId; + + return $this; + } +} diff --git a/src/PhpWord/ComplexType/TblWidth.php b/src/PhpWord/ComplexType/TblWidth.php new file mode 100644 index 0000000000..f1c0e10c9c --- /dev/null +++ b/src/PhpWord/ComplexType/TblWidth.php @@ -0,0 +1,60 @@ +value = $value; + TblWidthSimpleType::validate($type); + $this->type = $type; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return int + */ + public function getValue() + { + return $this->value; + } +} diff --git a/src/PhpWord/ComplexType/TrackChangesView.php b/src/PhpWord/ComplexType/TrackChangesView.php new file mode 100644 index 0000000000..a9797fad4a --- /dev/null +++ b/src/PhpWord/ComplexType/TrackChangesView.php @@ -0,0 +1,167 @@ +markup; + } + + /** + * Set Display Visual Indicator Of Markup Area. + * + * @param ?bool $markup + * Set to true to show markup + */ + public function setMarkup($markup): void + { + $this->markup = $markup === null ? true : $markup; + } + + /** + * Get Display Comments. + * + * @return bool True if comments are shown + */ + public function hasComments() + { + return $this->comments; + } + + /** + * Set Display Comments. + * + * @param ?bool $comments + * Set to true to show comments + */ + public function setComments($comments): void + { + $this->comments = $comments === null ? true : $comments; + } + + /** + * Get Display Content Revisions. + * + * @return bool True if content revisions are shown + */ + public function hasInsDel() + { + return $this->insDel; + } + + /** + * Set Display Content Revisions. + * + * @param ?bool $insDel + * Set to true to show content revisions + */ + public function setInsDel($insDel): void + { + $this->insDel = $insDel === null ? true : $insDel; + } + + /** + * Get Display Formatting Revisions. + * + * @return bool True if formatting revisions are shown + */ + public function hasFormatting() + { + return $this->formatting; + } + + /** + * Set Display Formatting Revisions. + * + * @param null|bool $formatting + * Set to true to show formatting revisions + */ + public function setFormatting($formatting = null): void + { + $this->formatting = $formatting === null ? true : $formatting; + } + + /** + * Get Display Ink Annotations. + * + * @return bool True if ink annotations are shown + */ + public function hasInkAnnotations() + { + return $this->inkAnnotations; + } + + /** + * Set Display Ink Annotations. + * + * @param ?bool $inkAnnotations + * Set to true to show ink annotations + */ + public function setInkAnnotations($inkAnnotations): void + { + $this->inkAnnotations = $inkAnnotations === null ? true : $inkAnnotations; + } +} diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index 57d646a01b..37140b4582 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -1,4 +1,5 @@ addElement($element, $fontStyle, $paragraphStyle); } - - // All other elements } else { + // All other elements array_unshift($args, $element); // Prepend element name to the beginning of args array - return call_user_func_array(array($this, 'addElement'), $args); + + return call_user_func_array([$this, 'addElement'], $args); } } @@ -117,12 +126,13 @@ public function __call($function, $args) } /** - * Add element + * Add element. * * Each element has different number of parameters passed * * @param string $elementName - * @return \PhpOffice\PhpWord\Element\AbstractElement + * + * @return AbstractElement */ protected function addElement($elementName) { @@ -131,17 +141,17 @@ protected function addElement($elementName) // Get arguments $args = func_get_args(); - $withoutP = in_array($this->container, array('TextRun', 'Footnote', 'Endnote', 'ListItemRun', 'Field')); + $withoutP = in_array($this->container, ['TextRun', 'Footnote', 'Endnote', 'ListItemRun', 'Field']); if ($withoutP && ($elementName == 'Text' || $elementName == 'PreserveText')) { $args[3] = null; // Remove paragraph style for texts in textrun } // Create element using reflection - $reflection = new \ReflectionClass($elementClass); + $reflection = new ReflectionClass($elementClass); $elementArgs = $args; array_shift($elementArgs); // Shift the $elementName off the beginning of array - /** @var \PhpOffice\PhpWord\Element\AbstractElement $element Type hint */ + /** @var AbstractElement $element Type hint */ $element = $reflection->newInstanceArgs($elementArgs); // Set parent container @@ -155,9 +165,9 @@ protected function addElement($elementName) } /** - * Get all elements + * Get all elements. * - * @return array + * @return AbstractElement[] */ public function getElements() { @@ -165,7 +175,43 @@ public function getElements() } /** - * Count elements + * Returns the element at the requested position. + * + * @param int $index + * + * @return null|AbstractElement + */ + public function getElement($index) + { + if (array_key_exists($index, $this->elements)) { + return $this->elements[$index]; + } + + return null; + } + + /** + * Removes the element at requested index. + * + * @param AbstractElement|int $toRemove + */ + public function removeElement($toRemove): void + { + if (is_int($toRemove) && array_key_exists($toRemove, $this->elements)) { + unset($this->elements[$toRemove]); + } elseif ($toRemove instanceof AbstractElement) { + foreach ($this->elements as $key => $element) { + if ($element->getElementId() === $toRemove->getElementId()) { + unset($this->elements[$key]); + + return; + } + } + } + } + + /** + * Count elements. * * @return int */ @@ -175,57 +221,58 @@ public function countElements() } /** - * Check if a method is allowed for the current container + * Check if a method is allowed for the current container. * * @param string $method + * * @return bool - * @throws \BadMethodCallException */ private function checkValidity($method) { - $generalContainers = array( - 'Section', 'Header', 'Footer', 'Footnote', 'Endnote', 'Cell', 'TextRun', 'TextBox', 'ListItemRun', - ); + $generalContainers = [ + 'Section', 'Header', 'Footer', 'Footnote', 'Endnote', 'Cell', 'TextRun', 'TextBox', 'ListItemRun', 'TrackChange', + ]; - $validContainers = array( - 'Text' => $generalContainers, - 'Bookmark' => $generalContainers, - 'Link' => $generalContainers, - 'TextBreak' => $generalContainers, - 'Image' => $generalContainers, - 'Object' => $generalContainers, - 'Field' => $generalContainers, - 'Line' => $generalContainers, - 'Shape' => $generalContainers, - 'FormField' => $generalContainers, - 'SDT' => $generalContainers, - 'TextRun' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'), - 'ListItem' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'), - 'ListItemRun' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'), - 'Table' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'), - 'CheckBox' => array('Section', 'Header', 'Footer', 'Cell'), - 'TextBox' => array('Section', 'Header', 'Footer', 'Cell'), - 'Footnote' => array('Section', 'TextRun', 'Cell'), - 'Endnote' => array('Section', 'TextRun', 'Cell'), - 'PreserveText' => array('Header', 'Footer', 'Cell'), - 'Title' => array('Section'), - 'TOC' => array('Section'), - 'PageBreak' => array('Section'), - 'Chart' => array('Section'), - ); + $validContainers = [ + 'Text' => $generalContainers, + 'Bookmark' => $generalContainers, + 'Link' => $generalContainers, + 'TextBreak' => $generalContainers, + 'Image' => $generalContainers, + 'OLEObject' => $generalContainers, + 'Field' => $generalContainers, + 'Line' => $generalContainers, + 'Shape' => $generalContainers, + 'FormField' => $generalContainers, + 'SDT' => $generalContainers, + 'TrackChange' => $generalContainers, + 'TextRun' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox', 'TrackChange', 'ListItemRun'], + 'ListItem' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox'], + 'ListItemRun' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox'], + 'Table' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox'], + 'CheckBox' => ['Section', 'Header', 'Footer', 'Cell', 'TextRun'], + 'TextBox' => ['Section', 'Header', 'Footer', 'Cell'], + 'Footnote' => ['Section', 'TextRun', 'Cell', 'ListItemRun'], + 'Endnote' => ['Section', 'TextRun', 'Cell'], + 'PreserveText' => ['Section', 'Header', 'Footer', 'Cell'], + 'Title' => ['Section', 'Cell', 'Header'], + 'TOC' => ['Section'], + 'PageBreak' => ['Section'], + 'Chart' => ['Section', 'Cell'], + ]; // Special condition, e.g. preservetext can only exists in cell when // the cell is located in header or footer - $validSubcontainers = array( - 'PreserveText' => array(array('Cell'), array('Header', 'Footer')), - 'Footnote' => array(array('Cell', 'TextRun'), array('Section')), - 'Endnote' => array(array('Cell', 'TextRun'), array('Section')), - ); + $validSubcontainers = [ + 'PreserveText' => [['Cell'], ['Header', 'Footer', 'Section']], + 'Footnote' => [['Cell', 'TextRun'], ['Section']], + 'Endnote' => [['Cell', 'TextRun'], ['Section']], + ]; // Check if a method is valid for current container if (isset($validContainers[$method])) { if (!in_array($this->container, $validContainers[$method])) { - throw new \BadMethodCallException("Cannot add {$method} in {$this->container}."); + throw new BadMethodCallException("Cannot add {$method} in {$this->container}."); } } @@ -236,51 +283,11 @@ private function checkValidity($method) $allowedDocParts = $rules[1]; foreach ($containers as $container) { if ($this->container == $container && !in_array($this->getDocPart(), $allowedDocParts)) { - throw new \BadMethodCallException("Cannot add {$method} in {$this->container}."); + throw new BadMethodCallException("Cannot add {$method} in {$this->container}."); } } } return true; } - - /** - * Add memory image element - * - * @param string $src - * @param mixed $style - * @return \PhpOffice\PhpWord\Element\Image - * @deprecated 0.9.0 - * @codeCoverageIgnore - */ - public function addMemoryImage($src, $style = null) - { - return $this->addImage($src, $style); - } - - /** - * Create textrun element - * - * @param mixed $paragraphStyle - * @return \PhpOffice\PhpWord\Element\TextRun - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function createTextRun($paragraphStyle = null) - { - return $this->addTextRun($paragraphStyle); - } - - /** - * Create footnote element - * - * @param mixed $paragraphStyle - * @return \PhpOffice\PhpWord\Element\Footnote - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function createFootnote($paragraphStyle = null) - { - return $this->addFootnote($paragraphStyle); - } } diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index 99a7a1becc..3a29b68673 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -1,4 +1,5 @@ phpWord; } /** * Set PhpWord as reference. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord - * @return void */ - public function setPhpWord(PhpWord $phpWord = null) + public function setPhpWord(?PhpWord $phpWord = null): void { $this->phpWord = $phpWord; } /** - * Get section number + * Get section number. * * @return int */ @@ -151,16 +178,15 @@ public function getSectionId() * * @param string $docPart * @param int $docPartId - * @return void */ - public function setDocPart($docPart, $docPartId = 1) + public function setDocPart($docPart, $docPartId = 1): void { $this->docPart = $docPart; $this->docPartId = $docPartId; } /** - * Get doc part + * Get doc part. * * @return string */ @@ -170,7 +196,7 @@ public function getDocPart() } /** - * Get doc part Id + * Get doc part Id. * * @return int */ @@ -180,7 +206,7 @@ public function getDocPartId() } /** - * Return media element (image, object, link) container name + * Return media element (image, object, link) container name. * * @return string section|headerx|footerx|footnote|endnote */ @@ -195,7 +221,7 @@ private function getMediaPart() } /** - * Get element index + * Get element index. * * @return int */ @@ -208,15 +234,14 @@ public function getElementIndex() * Set element index. * * @param int $value - * @return void */ - public function setElementIndex($value) + public function setElementIndex($value): void { $this->elementIndex = $value; } /** - * Get element unique ID + * Get element unique ID. * * @return string */ @@ -227,16 +252,14 @@ public function getElementId() /** * Set element unique ID from 6 first digit of md5. - * - * @return void */ - public function setElementId() + public function setElementId(): void { - $this->elementId = substr(md5(rand()), 0, 6); + $this->elementId = substr(md5((string) mt_rand()), 0, 6); } /** - * Get relation Id + * Get relation Id. * * @return int */ @@ -249,15 +272,14 @@ public function getRelationId() * Set relation Id. * * @param int $value - * @return void */ - public function setRelationId($value) + public function setRelationId($value): void { $this->relationId = $value; } /** - * Get nested level + * Get nested level. * * @return int */ @@ -267,21 +289,117 @@ public function getNestedLevel() } /** - * Set parent container + * Get comments start. + */ + public function getCommentsRangeStart(): ?Comments + { + return $this->commentsRangeStart; + } + + /** + * Get comment start. + */ + public function getCommentRangeStart(): ?Comment + { + if ($this->commentsRangeStart != null) { + return $this->commentsRangeStart->getItem($this->commentsRangeStart->countItems()); + } + + return null; + } + + /** + * Set comment start. + */ + public function setCommentRangeStart(Comment $value): void + { + if ($this instanceof Comment) { + throw new InvalidArgumentException('Cannot set a Comment on a Comment'); + } + 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. + */ + public function getCommentsRangeEnd(): ?Comments + { + return $this->commentsRangeEnd; + } + + /** + * Get comment end. + */ + public function getCommentRangeEnd(): ?Comment + { + if ($this->commentsRangeEnd != null) { + return $this->commentsRangeEnd->getItem($this->commentsRangeEnd->countItems()); + } + + return null; + } + + /** + * Set comment end. + */ + public function setCommentRangeEnd(Comment $value): void + { + if ($this instanceof Comment) { + throw new InvalidArgumentException('Cannot set a Comment on a Comment'); + } + 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); + } + + /** + * Get parent element. * - * Passed parameter should be a container, except for Table (contain Row) and Row (contain Cell) + * @return null|AbstractElement + */ + public function getParent() + { + return $this->parent; + } + + /** + * Set parent container. * - * @param \PhpOffice\PhpWord\Element\AbstractElement $container - * @return void + * Passed parameter should be a container, except for Table (contain Row) and Row (contain Cell) */ - public function setParentContainer(AbstractElement $container) + public function setParentContainer(self $container): void { $this->parentContainer = substr(get_class($container), strrpos(get_class($container), '\\') + 1); + $this->parent = $container; // Set nested level $this->nestedLevel = $container->getNestedLevel(); if ($this->parentContainer == 'Cell') { - $this->nestedLevel++; + ++$this->nestedLevel; } // Set phpword @@ -297,20 +415,21 @@ public function setParentContainer(AbstractElement $container) } /** - * Set relation Id for media elements (link, image, object; legacy of OOXML) + * Set relation Id for media elements (link, image, object; legacy of OOXML). * * - Image element needs to be passed to Media object * - Icon needs to be set for Object element - * - * @return void */ - private function setMediaRelation() + private function setMediaRelation(): void { - if (!$this instanceof Link && !$this instanceof Image && !$this instanceof Object) { + if (!$this instanceof Link && !$this instanceof Image && !$this instanceof OLEObject) { return; } - $elementName = substr(get_class($this), strrpos(get_class($this), '\\') + 1); + $elementName = substr(static::class, strrpos(static::class, '\\') + 1); + if ($elementName == 'OLEObject') { + $elementName = 'Object'; + } $mediaPart = $this->getMediaPart(); $source = $this->getSource(); $image = null; @@ -320,7 +439,7 @@ private function setMediaRelation() $rId = Media::addElement($mediaPart, strtolower($elementName), $source, $image); $this->setRelationId($rId); - if ($this instanceof Object) { + if ($this instanceof OLEObject) { $icon = $this->getIcon(); $rId = Media::addElement($mediaPart, 'image', $icon, new Image($icon)); $this->setImageRelationId($rId); @@ -329,13 +448,11 @@ private function setMediaRelation() /** * Set relation Id for elements that will be registered in the Collection subnamespaces. - * - * @return void */ - private function setCollectionRelation() + private function setCollectionRelation(): void { if ($this->collectionRelation === true && $this->phpWord instanceof PhpWord) { - $elementName = substr(get_class($this), strrpos(get_class($this), '\\') + 1); + $elementName = substr(static::class, strrpos(static::class, '\\') + 1); $addMethod = "add{$elementName}"; $rId = $this->phpWord->$addMethod($this); $this->setRelationId($rId); @@ -343,26 +460,27 @@ private function setCollectionRelation() } /** - * Check if element is located in Section doc part (as opposed to Header/Footer) + * Check if element is located in Section doc part (as opposed to Header/Footer). * * @return bool */ public function isInSection() { - return ($this->docPart == 'Section'); + return $this->docPart == 'Section'; } /** - * Set new style value + * 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 */ protected function setNewStyle($styleObject, $styleValue = null, $returnObject = false) { - if (!is_null($styleValue) && is_array($styleValue)) { + if (null !== $styleValue && is_array($styleValue)) { $styleObject->setStyleByArray($styleValue); $style = $styleObject; } else { @@ -373,19 +491,50 @@ protected function setNewStyle($styleObject, $styleValue = null, $returnObject = } /** - * Set enum value + * Sets the trackChange information. + */ + public function setTrackChange(TrackChange $trackChange): void + { + $this->trackChange = $trackChange; + } + + /** + * Gets the trackChange information. + * + * @return TrackChange + */ + public function getTrackChange() + { + return $this->trackChange; + } + + /** + * Set changed. + * + * @param string $type INSERTED|DELETED + * @param string $author + * @param null|DateTime|int $date allways in UTC + */ + public function setChangeInfo($type, $author, $date = null): void + { + $this->trackChange = new TrackChange($type, $author, $date); + } + + /** + * Set enum value. + * + * @param null|string $value + * @param string[] $enum + * @param null|string $default + * + * @return null|string * - * @param mixed $value - * @param array $enum - * @param mixed $default - * @return mixed - * @throws \InvalidArgumentException * @todo Merge with the same method in AbstractStyle */ - protected function setEnumVal($value = null, $enum = array(), $default = null) + protected function setEnumVal($value = null, $enum = [], $default = null) { - if ($value != null && trim($value) != '' && !empty($enum) && !in_array($value, $enum)) { - throw new \InvalidArgumentException("Invalid style value: {$value}"); + if ($value !== null && trim($value) != '' && !empty($enum) && !in_array($value, $enum)) { + throw new InvalidArgumentException("Invalid style value: {$value}"); } elseif ($value === null || trim($value) == '') { $value = $default; } diff --git a/src/PhpWord/Element/Bookmark.php b/src/PhpWord/Element/Bookmark.php index d5b8ff6fba..151d5a48bf 100644 --- a/src/PhpWord/Element/Bookmark.php +++ b/src/PhpWord/Element/Bookmark.php @@ -1,4 +1,5 @@ name = String::toUTF8($name); - return $this; + $this->name = SharedText::toUTF8($name); } /** - * Get Bookmark name + * Get Bookmark name. * * @return string */ diff --git a/src/PhpWord/Element/Cell.php b/src/PhpWord/Element/Cell.php index cac37a7804..2adcc1ab54 100644 --- a/src/PhpWord/Element/Cell.php +++ b/src/PhpWord/Element/Cell.php @@ -1,4 +1,5 @@ setType($type); - $this->addSeries($categories, $values); + $this->addSeries($categories, $values, $seriesName); $this->style = $this->setNewStyle(new ChartStyle(), $style, true); } /** - * Get type + * Get type. * * @return string */ @@ -83,28 +85,31 @@ public function getType() * Set type. * * @param string $value - * @return void */ - public function setType($value) + public function setType($value): void { - $enum = array('pie', 'doughnut', 'line', 'bar', 'column', 'area', 'radar', 'scatter'); + $enum = ['pie', 'doughnut', 'line', 'bar', 'stacked_bar', 'percent_stacked_bar', 'column', 'stacked_column', 'percent_stacked_column', 'area', 'radar', 'scatter']; $this->type = $this->setEnumVal($value, $enum, 'pie'); } /** - * Add series + * Add series. * * @param array $categories * @param array $values - * @return void + * @param null|mixed $name */ - public function addSeries($categories, $values) + public function addSeries($categories, $values, $name = null): void { - $this->series[] = array('categories' => $categories, 'values' => $values); + $this->series[] = [ + 'categories' => $categories, + 'values' => $values, + 'name' => $name, + ]; } /** - * Get series + * Get series. * * @return array */ @@ -114,9 +119,9 @@ public function getSeries() } /** - * Get chart style + * Get chart style. * - * @return \PhpOffice\PhpWord\Style\Chart + * @return ?ChartStyle */ public function getStyle() { diff --git a/src/PhpWord/Element/CheckBox.php b/src/PhpWord/Element/CheckBox.php index d3b2a3c6f4..b404a6027e 100644 --- a/src/PhpWord/Element/CheckBox.php +++ b/src/PhpWord/Element/CheckBox.php @@ -1,4 +1,5 @@ name = String::toUTF8($name); + $this->name = SharedText::toUTF8($name); return $this; } /** - * Get name content + * Get name content. * * @return string */ diff --git a/src/PhpWord/Element/Comment.php b/src/PhpWord/Element/Comment.php new file mode 100644 index 0000000000..a6a2e29d56 --- /dev/null +++ b/src/PhpWord/Element/Comment.php @@ -0,0 +1,118 @@ +initials = $initials; + } + + /** + * Get Initials. + * + * @return string + */ + public function getInitials() + { + return $this->initials; + } + + /** + * Sets the element where this comment starts. + */ + public function setStartElement(AbstractElement $value): void + { + $this->startElement = $value; + $value->setCommentRangeStart($this); + } + + /** + * Get the element where this comment starts. + * + * @return AbstractElement + */ + public function getStartElement() + { + return $this->startElement; + } + + /** + * Sets the element where this comment ends. + */ + public function setEndElement(AbstractElement $value): void + { + $this->endElement = $value; + $value->setCommentRangeEnd($this); + } + + /** + * Get the element where this comment ends. + * + * @return AbstractElement + */ + public function getEndElement() + { + return $this->endElement; + } +} diff --git a/src/PhpWord/Element/Endnote.php b/src/PhpWord/Element/Endnote.php index 1000055520..d2f42eafd0 100644 --- a/src/PhpWord/Element/Endnote.php +++ b/src/PhpWord/Element/Endnote.php @@ -1,4 +1,5 @@ paragraphStyle = $this->setNewStyle(new Paragraph(), $paragraphStyle); + parent::__construct($paragraphStyle); } } diff --git a/src/PhpWord/Element/Field.php b/src/PhpWord/Element/Field.php index 1eaa6f242f..592d6e421c 100644 --- a/src/PhpWord/Element/Field.php +++ b/src/PhpWord/Element/Field.php @@ -1,4 +1,5 @@ array( - 'properties'=>array( - 'format' => array('Arabic', 'ArabicDash', 'alphabetic', 'ALPHABETIC', 'roman', 'ROMAN'), - ), - 'options'=>array('PreserveFormat') - ), - 'NUMPAGES'=>array( - 'properties'=>array( - 'format' => array('Arabic', 'ArabicDash', 'alphabetic', 'ALPHABETIC', 'roman', 'ROMAN'), - 'numformat' => array('0', '0,00', '#.##0', '#.##0,00', '€ #.##0,00(€ #.##0,00)', '0%', '0,00%') - ), - 'options'=>array('PreserveFormat') - ), - 'DATE'=>array( - 'properties'=> array( - 'dateformat' =>array('d-M-yyyy', 'dddd d MMMM yyyy', 'd MMMM yyyy', 'd-M-yy', 'yyyy-MM-dd', - 'd-MMM-yy', 'd/M/yyyy', 'd MMM. yy', 'd/M/yy', 'MMM-yy', 'd-M-yyy H:mm', 'd-M-yyyy H:mm:ss', - 'h:mm am/pm', 'h:mm:ss am/pm', 'HH:mm', 'HH:mm:ss') - ), - 'options'=>array('PreserveFormat', 'LunarCalendar', 'SakaEraCalendar', 'LastUsedFormat') - ) - ); - - /** - * Field type + protected $fieldsArray = [ + 'PAGE' => [ + 'properties' => [ + 'format' => ['Arabic', 'ArabicDash', 'alphabetic', 'ALPHABETIC', 'roman', 'ROMAN'], + ], + 'options' => ['PreserveFormat'], + ], + 'NUMPAGES' => [ + 'properties' => [ + 'format' => ['Arabic', 'ArabicDash', 'CardText', 'DollarText', 'Ordinal', 'OrdText', + 'alphabetic', 'ALPHABETIC', 'roman', 'ROMAN', 'Caps', 'FirstCap', 'Lower', 'Upper', ], + 'numformat' => ['0', '0,00', '#.##0', '#.##0,00', '€ #.##0,00(€ #.##0,00)', '0%', '0,00%'], + ], + 'options' => ['PreserveFormat'], + ], + 'DATE' => [ + 'properties' => [ + 'dateformat' => [ + // Generic formats + 'yyyy-MM-dd', 'yyyy-MM', 'MMM-yy', 'MMM-yyyy', 'h:mm am/pm', 'h:mm:ss am/pm', 'HH:mm', 'HH:mm:ss', + // Day-Month-Year formats + 'dddd d MMMM yyyy', 'd MMMM yyyy', 'd-MMM-yy', 'd MMM. yy', + 'd-M-yy', 'd-M-yy h:mm', 'd-M-yy h:mm:ss', 'd-M-yy h:mm am/pm', 'd-M-yy h:mm:ss am/pm', 'd-M-yy HH:mm', 'd-M-yy HH:mm:ss', + 'd/M/yy', 'd/M/yy h:mm', 'd/M/yy h:mm:ss', 'd/M/yy h:mm am/pm', 'd/M/yy h:mm:ss am/pm', 'd/M/yy HH:mm', 'd/M/yy HH:mm:ss', + 'd-M-yyyy', 'd-M-yyyy h:mm', 'd-M-yyyy h:mm:ss', 'd-M-yyyy h:mm am/pm', 'd-M-yyyy h:mm:ss am/pm', 'd-M-yyyy HH:mm', 'd-M-yyyy HH:mm:ss', + 'd/M/yyyy', 'd/M/yyyy h:mm', 'd/M/yyyy h:mm:ss', 'd/M/yyyy h:mm am/pm', 'd/M/yyyy h:mm:ss am/pm', 'd/M/yyyy HH:mm', 'd/M/yyyy HH:mm:ss', + // Month-Day-Year formats + 'dddd, MMMM d yyyy', 'MMMM d yyyy', 'MMM-d-yy', 'MMM. d yy', + 'M-d-yy', 'M-d-yy h:mm', 'M-d-yy h:mm:ss', 'M-d-yy h:mm am/pm', 'M-d-yy h:mm:ss am/pm', 'M-d-yy HH:mm', 'M-d-yy HH:mm:ss', + 'M/d/yy', 'M/d/yy h:mm', 'M/d/yy h:mm:ss', 'M/d/yy h:mm am/pm', 'M/d/yy h:mm:ss am/pm', 'M/d/yy HH:mm', 'M/d/yy HH:mm:ss', + 'M-d-yyyy', 'M-d-yyyy h:mm', 'M-d-yyyy h:mm:ss', 'M-d-yyyy h:mm am/pm', 'M-d-yyyy h:mm:ss am/pm', 'M-d-yyyy HH:mm', 'M-d-yyyy HH:mm:ss', + 'M/d/yyyy', 'M/d/yyyy h:mm', 'M/d/yyyy h:mm:ss', 'M/d/yyyy h:mm am/pm', 'M/d/yyyy h:mm:ss am/pm', 'M/d/yyyy HH:mm', 'M/d/yyyy HH:mm:ss', + ], + ], + 'options' => ['PreserveFormat', 'LunarCalendar', 'SakaEraCalendar', 'LastUsedFormat'], + ], + 'MACROBUTTON' => [ + 'properties' => ['macroname' => ''], + ], + 'XE' => [ + 'properties' => [], + 'options' => ['Bold', 'Italic'], + ], + 'INDEX' => [ + 'properties' => [], + 'options' => ['PreserveFormat'], + ], + 'STYLEREF' => [ + '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'], + ], + ]; + + /** + * Field type. * * @var string */ protected $type; /** - * Field properties + * Field text. + * + * @var null|string|TextRun + */ + protected $text; + + /** + * Field properties. * * @var array */ - protected $properties = array(); + protected $properties = []; /** - * Field options + * Field options. * * @var array */ - protected $options = array(); + protected $options = []; /** - * Create a new Field Element + * Font style. + * + * @var Font|string + */ + protected $fontStyle; + + /** + * Set Font style. + * + * @param array|Font|string $style + * + * @return Font|string + */ + public function setFontStyle($style = null) + { + if ($style instanceof Font) { + $this->fontStyle = $style; + } elseif (is_array($style)) { + $this->fontStyle = new Font('text'); + $this->fontStyle->setStyleByArray($style); + } elseif (null === $style) { + $this->fontStyle = null; + } else { + $this->fontStyle = $style; + } + + return $this->fontStyle; + } + + /** + * Get Font style. + * + * @return Font|string + */ + public function getFontStyle() + { + return $this->fontStyle; + } + + /** + * Create a new Field Element. * * @param string $type * @param array $properties * @param array $options + * @param null|string|TextRun $text + * @param array|Font|string $fontStyle */ - public function __construct($type = null, $properties = array(), $options = array()) + public function __construct($type = null, $properties = [], $options = [], $text = null, $fontStyle = null) { $this->setType($type); $this->setProperties($properties); $this->setOptions($options); + $this->setText($text); + $this->setFontStyle($fontStyle); } /** - * Set Field type + * Set Field type. * * @param string $type + * * @return string - * @throws \InvalidArgumentException */ public function setType($type = null) { @@ -103,14 +197,15 @@ public function setType($type = null) if (isset($this->fieldsArray[$type])) { $this->type = $type; } else { - throw new \InvalidArgumentException("Invalid type"); + throw new InvalidArgumentException("Invalid type '$type'"); } } + return $this->type; } /** - * Get Field type + * Get Field type. * * @return string */ @@ -120,27 +215,24 @@ public function getType() } /** - * Set Field properties + * Set Field properties. * - * @param array $properties * @return self - * @throws \InvalidArgumentException */ - public function setProperties($properties = array()) + public function setProperties(array $properties = []) { - if (is_array($properties)) { - foreach (array_keys($properties) as $propkey) { - if (!(isset($this->fieldsArray[$this->type]['properties'][$propkey]))) { - throw new \InvalidArgumentException("Invalid property"); - } + foreach (array_keys($properties) as $propkey) { + if (!(isset($this->fieldsArray[$this->type]['properties'][$propkey]))) { + throw new InvalidArgumentException("Invalid property '$propkey'"); } - $this->properties = array_merge($this->properties, $properties); } - return $this->properties; + $this->properties = array_merge($this->properties, $properties); + + return $this; } /** - * Get Field properties + * Get Field properties. * * @return array */ @@ -150,27 +242,24 @@ public function getProperties() } /** - * Set Field options + * Set Field options. * - * @param array $options * @return self - * @throws \InvalidArgumentException */ - public function setOptions($options = array()) + public function setOptions(array $options = []) { - if (is_array($options)) { - foreach (array_keys($options) as $optionkey) { - if (!(isset($this->fieldsArray[$this->type]['options'][$optionkey]))) { - throw new \InvalidArgumentException("Invalid option"); - } + foreach (array_keys($options) as $optionkey) { + if (!(isset($this->fieldsArray[$this->type]['options'][$optionkey])) && substr($optionkey, 0, 1) !== '\\') { + throw new InvalidArgumentException("Invalid option '$optionkey', possible values are " . implode(', ', $this->fieldsArray[$this->type]['options'])); } - $this->options = array_merge($this->options, $options); } - return $this->options; + $this->options = array_merge($this->options, $options); + + return $this; } /** - * Get Field properties + * Get Field properties. * * @return array */ @@ -178,4 +267,34 @@ public function getOptions() { return $this->options; } + + /** + * Set Field text. + * + * @param null|mixed|string|TextRun $text + * + * @return null|string|TextRun + */ + public function setText($text = null) + { + if (null !== $text) { + if (is_string($text) || $text instanceof TextRun) { + $this->text = $text; + } else { + throw new InvalidArgumentException('Invalid text'); + } + } + + return $this->text; + } + + /** + * Get Field text. + * + * @return string|TextRun + */ + public function getText() + { + return $this->text; + } } diff --git a/src/PhpWord/Element/Footer.php b/src/PhpWord/Element/Footer.php index 8e19eaf7db..d17db10a32 100644 --- a/src/PhpWord/Element/Footer.php +++ b/src/PhpWord/Element/Footer.php @@ -1,4 +1,5 @@ type = $value; } /** - * Get type + * Get type. * * @return string + * * @since 0.10.0 */ public function getType() @@ -86,7 +88,7 @@ public function getType() } /** - * Reset type to default + * Reset type to default. * * @return string */ @@ -96,7 +98,7 @@ public function resetType() } /** - * First page only header + * First page only header. * * @return string */ @@ -106,7 +108,7 @@ public function firstPage() } /** - * Even numbered pages only + * Even numbered pages only. * * @return string */ diff --git a/src/PhpWord/Element/Footnote.php b/src/PhpWord/Element/Footnote.php index 162a703ed5..6f959c2432 100644 --- a/src/PhpWord/Element/Footnote.php +++ b/src/PhpWord/Element/Footnote.php @@ -1,4 +1,5 @@ paragraphStyle; } - - /** - * Get Footnote Reference ID - * - * @return int - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getReferenceId() - { - return $this->getRelationId(); - } - - /** - * Set Footnote Reference ID - * - * @param int $rId - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function setReferenceId($rId) - { - $this->setRelationId($rId); - } } diff --git a/src/PhpWord/Element/FormField.php b/src/PhpWord/Element/FormField.php index 7bd61be1e7..5318134584 100644 --- a/src/PhpWord/Element/FormField.php +++ b/src/PhpWord/Element/FormField.php @@ -1,4 +1,5 @@ setType($type); } /** - * Get type + * Get type. * * @return string */ @@ -88,23 +89,24 @@ public function getType() } /** - * Set type + * Set type. * * @param string $value + * * @return self */ public function setType($value) { - $enum = array('textinput', 'checkbox', 'dropdown'); + $enum = ['textinput', 'checkbox', 'dropdown']; $this->type = $this->setEnumVal($value, $enum, $this->type); return $this; } /** - * Get name + * Get name. * - * @return string + * @return ?string */ public function getName() { @@ -112,9 +114,10 @@ public function getName() } /** - * Set name + * Set name. + * + * @param ?string $value * - * @param string|bool|int $value * @return self */ public function setName($value) @@ -125,9 +128,9 @@ public function setName($value) } /** - * Get default + * Get default. * - * @return string|bool|int + * @return bool|int|string */ public function getDefault() { @@ -135,9 +138,10 @@ public function getDefault() } /** - * Set default + * Set default. + * + * @param bool|int|string $value * - * @param string|bool|int $value * @return self */ public function setDefault($value) @@ -148,9 +152,9 @@ public function setDefault($value) } /** - * Get value + * Get value. * - * @return string|bool|int + * @return null|bool|int|string */ public function getValue() { @@ -158,9 +162,10 @@ public function getValue() } /** - * Set value + * Set value. + * + * @param null|bool|int|string $value * - * @param string|bool|int $value * @return self */ public function setValue($value) @@ -171,7 +176,7 @@ public function setValue($value) } /** - * Get entries + * Get entries. * * @return array */ @@ -181,9 +186,10 @@ public function getEntries() } /** - * Set entries + * Set entries. * * @param array $value + * * @return self */ public function setEntries($value) diff --git a/tests/PhpWord/Tests/Reader/ODTextTest.php b/src/PhpWord/Element/Formula.php similarity index 50% rename from tests/PhpWord/Tests/Reader/ODTextTest.php rename to src/PhpWord/Element/Formula.php index fc4d2e337e..76bef4f683 100644 --- a/tests/PhpWord/Tests/Reader/ODTextTest.php +++ b/src/PhpWord/Element/Formula.php @@ -1,4 +1,5 @@ setMath($math); + } + + public function setMath(Math $math): self + { + $this->math = $math; + + return $this; + } + + public function getMath(): Math { - $filename = __DIR__ . '/../_files/documents/reader.odt'; - $phpWord = IOFactory::load($filename, 'ODText'); - $this->assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); + return $this->math; } } diff --git a/src/PhpWord/Element/Header.php b/src/PhpWord/Element/Header.php index feaa86e81a..81a493f3f2 100644 --- a/src/PhpWord/Element/Header.php +++ b/src/PhpWord/Element/Header.php @@ -1,4 +1,5 @@ source = $source; - $this->setIsWatermark($watermark); $this->style = $this->setNewStyle(new ImageStyle(), $style, true); + $this->setIsWatermark($watermark); + $this->setName($name); - $this->checkImage($source); + $this->checkImage(); } /** - * Get Image style + * Get Image style. * - * @return ImageStyle + * @return ?ImageStyle */ public function getStyle() { @@ -149,7 +168,7 @@ public function getStyle() } /** - * Get image source + * Get image source. * * @return string */ @@ -159,7 +178,7 @@ public function getSource() } /** - * Get image source type + * Get image source type. * * @return string */ @@ -169,7 +188,27 @@ public function getSourceType() } /** - * Get image media ID + * Sets the image name. + * + * @param string $value + */ + public function setName($value): void + { + $this->name = $value; + } + + /** + * Get image name. + * + * @return null|string + */ + public function getName() + { + return $this->name; + } + + /** + * Get image media ID. * * @return string */ @@ -179,9 +218,9 @@ public function getMediaId() } /** - * Get is watermark + * Get is watermark. * - * @return boolean + * @return bool */ public function isWatermark() { @@ -189,17 +228,17 @@ public function isWatermark() } /** - * Set is watermark + * Set is watermark. * - * @param boolean $value + * @param bool $value */ - public function setIsWatermark($value) + public function setIsWatermark($value): void { $this->watermark = $value; } /** - * Get image type + * Get image type. * * @return string */ @@ -209,7 +248,7 @@ public function getImageType() } /** - * Get image create function + * Get image create function. * * @return string */ @@ -219,17 +258,25 @@ public function getImageCreateFunction() } /** - * Get image function + * Get image function. * - * @return string + * @return null|callable(resource): void */ - public function getImageFunction() + public function getImageFunction(): ?callable { return $this->imageFunc; } /** - * Get image extension + * Get image quality. + */ + public function getImageQuality(): ?int + { + return $this->imageQuality; + } + + /** + * Get image extension. * * @return string */ @@ -239,9 +286,9 @@ public function getImageExtension() } /** - * Get is memory image + * Get is memory image. * - * @return boolean + * @return bool */ public function isMemImage() { @@ -249,7 +296,7 @@ public function isMemImage() } /** - * Get target file name + * Get target file name. * * @return string */ @@ -262,17 +309,16 @@ public function getTarget() * Set target file name. * * @param string $value - * @return void */ - public function setTarget($value) + public function setTarget($value): void { $this->target = $value; } /** - * Get media index + * Get media index. * - * @return integer + * @return int */ public function getMediaIndex() { @@ -282,38 +328,32 @@ public function getMediaIndex() /** * Set media index. * - * @param integer $value - * @return void + * @param int $value */ - public function setMediaIndex($value) + public function setMediaIndex($value): void { $this->mediaIndex = $value; } /** - * Get image string data - * - * @param bool $base64 - * @return string|null - * @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 // Return null if not found if ($this->sourceType == self::SOURCE_ARCHIVE) { $source = substr($source, 6); - list($zipFilename, $imageFilename) = explode('#', $source); + [$zipFilename, $imageFilename] = explode('#', $source); $zip = new ZipArchive(); if ($zip->open($zipFilename) !== false) { - if ($zip->locateName($imageFilename)) { + if ($zip->locateName($imageFilename) !== false) { $isTemp = true; $zip->extractTo(Settings::getTempDir(), $imageFilename); $actualSource = Settings::getTempDir() . DIRECTORY_SEPARATOR . $imageFilename; @@ -334,60 +374,81 @@ public function getImageStringData($base64 = false) // Read image binary data and convert to hex/base64 string if ($this->sourceType == self::SOURCE_GD) { $imageResource = call_user_func($this->imageCreateFunc, $actualSource); + if ($this->imageType === 'image/png') { + // PNG images need to preserve alpha channel information + 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; } /** - * Check memory image, supported type, image functions, and proportional width/height. + * Get image string data. * - * @param string $source - * @return void - * @throws \PhpOffice\PhpWord\Exception\InvalidImageException - * @throws \PhpOffice\PhpWord\Exception\UnsupportedImageTypeException + * @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); + } + + /** + * Check memory image, supported type, image functions, and proportional width/height. */ - private function checkImage($source) + private function checkImage(): void { - $this->setSourceType($source); + $this->setSourceType(); // Check image data if ($this->sourceType == self::SOURCE_ARCHIVE) { - $imageData = $this->getArchiveImageSize($source); + $imageData = $this->getArchiveImageSize($this->source); + } elseif ($this->sourceType == self::SOURCE_STRING) { + $imageData = @getimagesizefromstring($this->source); } else { - $imageData = @getimagesize($source); + $imageData = @getimagesize($this->source); } if (!is_array($imageData)) { - throw new InvalidImageException(); + throw new InvalidImageException(sprintf('Invalid image: %s', $this->source)); } - list($actualWidth, $actualHeight, $imageType) = $imageData; + [$actualWidth, $actualHeight, $imageType] = $imageData; // Check image type support - $supportedTypes = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG); - if ($this->sourceType != self::SOURCE_GD) { - $supportedTypes = array_merge($supportedTypes, array(IMAGETYPE_BMP, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM)); + $supportedTypes = [IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG]; + if ($this->sourceType != self::SOURCE_GD && $this->sourceType != self::SOURCE_STRING) { + $supportedTypes = array_merge($supportedTypes, [IMAGETYPE_BMP, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM]); } if (!in_array($imageType, $supportedTypes)) { throw new UnsupportedImageTypeException(); @@ -401,47 +462,56 @@ private function checkImage($source) /** * Set source type. - * - * @param string $source - * @return void */ - private function setSourceType($source) + private function setSourceType(): void { - if (stripos(strrev($source), strrev('.php')) === 0) { + if (stripos(strrev($this->source), strrev('.php')) === 0) { $this->memoryImage = true; $this->sourceType = self::SOURCE_GD; - } elseif (strpos($source, 'zip://') !== false) { + } elseif (strpos($this->source, 'zip://') !== false) { $this->memoryImage = false; $this->sourceType = self::SOURCE_ARCHIVE; + } elseif (filter_var($this->source, FILTER_VALIDATE_URL) !== false) { + $this->memoryImage = true; + if (strpos($this->source, 'https') === 0) { + $fileContent = file_get_contents($this->source); + $this->source = $fileContent; + $this->sourceType = self::SOURCE_STRING; + } else { + $this->sourceType = self::SOURCE_GD; + } + } elseif ((strpos($this->source, chr(0)) === false) && @file_exists($this->source)) { + $this->memoryImage = false; + $this->sourceType = self::SOURCE_LOCAL; } else { - $this->memoryImage = (filter_var($source, FILTER_VALIDATE_URL) !== false); - $this->sourceType = $this->memoryImage ? self::SOURCE_GD : self::SOURCE_LOCAL; + $this->memoryImage = true; + $this->sourceType = self::SOURCE_STRING; } } /** - * Get image size from archive + * Get image size from archive. * * @since 0.12.0 Throws CreateTemporaryFileException. * * @param string $source - * @return array|null - * @throws \PhpOffice\PhpWord\Exception\CreateTemporaryFileException + * + * @return null|array */ private function getArchiveImageSize($source) { $imageData = null; $source = substr($source, 6); - list($zipFilename, $imageFilename) = explode('#', $source); + [$zipFilename, $imageFilename] = explode('#', $source); $tempFilename = tempnam(Settings::getTempDir(), 'PHPWordImage'); if (false === $tempFilename) { - throw new CreateTemporaryFileException(); + throw new CreateTemporaryFileException(); // @codeCoverageIgnore } $zip = new ZipArchive(); if ($zip->open($zipFilename) !== false) { - if ($zip->locateName($imageFilename)) { + if ($zip->locateName($imageFilename) !== false) { $imageContent = $zip->getFromName($imageFilename); if ($imageContent !== false) { file_put_contents($tempFilename, $imageContent); @@ -457,35 +527,52 @@ private function getArchiveImageSize($source) /** * Set image functions and extensions. - * - * @return void */ - private function setFunctions() + private function setFunctions(): void { switch ($this->imageType) { case 'image/png': - $this->imageCreateFunc = 'imagecreatefrompng'; - $this->imageFunc = 'imagepng'; + $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefrompng'; + $this->imageFunc = function ($resource): void { + imagepng($resource, null, $this->imageQuality); + }; $this->imageExtension = 'png'; + $this->imageQuality = -1; + break; case 'image/gif': - $this->imageCreateFunc = 'imagecreatefromgif'; - $this->imageFunc = 'imagegif'; + $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefromgif'; + $this->imageFunc = function ($resource): void { + imagegif($resource); + }; $this->imageExtension = 'gif'; + $this->imageQuality = null; + break; case 'image/jpeg': case 'image/jpg': - $this->imageCreateFunc = 'imagecreatefromjpeg'; - $this->imageFunc = 'imagejpeg'; + $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefromjpeg'; + $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; } } @@ -493,11 +580,10 @@ private function setFunctions() /** * Set proportional width/height if one dimension not available. * - * @param integer $actualWidth - * @param integer $actualHeight - * @return void + * @param int $actualWidth + * @param int $actualHeight */ - private function setProportionalSize($actualWidth, $actualHeight) + private function setProportionalSize($actualWidth, $actualHeight): void { $styleWidth = $this->style->getWidth(); $styleHeight = $this->style->getHeight(); @@ -512,26 +598,4 @@ private function setProportionalSize($actualWidth, $actualHeight) } } } - - /** - * Get is watermark - * - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getIsWatermark() - { - return $this->isWatermark(); - } - - /** - * Get is memory image - * - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getIsMemImage() - { - return $this->isMemImage(); - } } diff --git a/src/PhpWord/Element/Line.php b/src/PhpWord/Element/Line.php index b76ac4fb32..b3ad438197 100644 --- a/src/PhpWord/Element/Line.php +++ b/src/PhpWord/Element/Line.php @@ -1,4 +1,5 @@ source = String::toUTF8($source); - $this->text = is_null($text) ? $this->source : String::toUTF8($text); + $this->source = SharedText::toUTF8($source); + $this->text = null === $text ? $this->source : SharedText::toUTF8($text); $this->fontStyle = $this->setNewStyle(new Font('text'), $fontStyle); $this->paragraphStyle = $this->setNewStyle(new Paragraph(), $paragraphStyle); $this->internal = $internal; - return $this; } /** - * Get link source + * Get link source. * * @return string */ @@ -97,19 +98,17 @@ public function getSource() } /** - * Get link text - * - * @return string + * Get link text. */ - public function getText() + public function getText(): string { return $this->text; } /** - * Get Text style + * Get Text style. * - * @return string|\PhpOffice\PhpWord\Style\Font + * @return null|Font|string */ public function getFontStyle() { @@ -117,9 +116,9 @@ public function getFontStyle() } /** - * Get Paragraph style + * Get Paragraph style. * - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return null|Paragraph|string */ public function getParagraphStyle() { @@ -127,43 +126,7 @@ public function getParagraphStyle() } /** - * Get link target - * - * @return string - * @deprecated 0.12.0 - * @codeCoverageIgnore - */ - public function getTarget() - { - return $this->source; - } - - /** - * Get Link source - * - * @return string - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getLinkSrc() - { - return $this->getSource(); - } - - /** - * Get Link name - * - * @return string - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getLinkName() - { - return $this->getText(); - } - - /** - * is internal + * is internal. * * @return bool */ diff --git a/src/PhpWord/Element/ListItem.php b/src/PhpWord/Element/ListItem.php index 4cb8d33952..bedfd110b6 100644 --- a/src/PhpWord/Element/ListItem.php +++ b/src/PhpWord/Element/ListItem.php @@ -1,4 +1,5 @@ textObject = new Text(String::toUTF8($text), $fontStyle, $paragraphStyle); + $this->textObject = new Text(SharedText::toUTF8($text), $fontStyle, $paragraphStyle); $this->depth = $depth; // Version >= 0.10.0 will pass numbering style name. Older version will use old method - if (!is_null($listStyle) && is_string($listStyle)) { - $this->style = new ListItemStyle($listStyle); + if (null !== $listStyle && is_string($listStyle)) { + $this->style = new ListItemStyle($listStyle); // @codeCoverageIgnore } else { $this->style = $this->setNewStyle(new ListItemStyle(), $listStyle, true); } } /** - * Get style + * Get style. * - * @return \PhpOffice\PhpWord\Style\ListItem + * @return ?ListItemStyle */ public function getStyle() { @@ -79,9 +80,9 @@ public function getStyle() } /** - * Get Text object + * Get Text object. * - * @return \PhpOffice\PhpWord\Element\Text + * @return Text */ public function getTextObject() { @@ -89,7 +90,7 @@ public function getTextObject() } /** - * Get depth + * Get depth. * * @return int */ @@ -99,9 +100,10 @@ public function getDepth() } /** - * Get text + * Get text. * * @return string + * * @since 0.11.0 */ public function getText() diff --git a/src/PhpWord/Element/ListItemRun.php b/src/PhpWord/Element/ListItemRun.php index 1b77830db3..01d0947b81 100644 --- a/src/PhpWord/Element/ListItemRun.php +++ b/src/PhpWord/Element/ListItemRun.php @@ -1,27 +1,27 @@ depth = $depth; // Version >= 0.10.0 will pass numbering style name. Older version will use old method - if (!is_null($listStyle) && is_string($listStyle)) { + if (null !== $listStyle && is_string($listStyle)) { $this->style = new ListItemStyle($listStyle); } else { $this->style = $this->setNewStyle(new ListItemStyle(), $listStyle, true); } - $this->paragraphStyle = $this->setNewStyle(new Paragraph(), $paragraphStyle); + parent::__construct($paragraphStyle); } /** * Get ListItem style. * - * @return \PhpOffice\PhpWord\Style\ListItem + * @return ?ListItemStyle */ public function getStyle() { return $this->style; } - /** + /** * Get ListItem depth. - * - * @return int + * + * @return int */ public function getDepth() { diff --git a/src/PhpWord/Element/Object.php b/src/PhpWord/Element/OLEObject.php similarity index 56% rename from src/PhpWord/Element/Object.php rename to src/PhpWord/Element/OLEObject.php index 31943ba68a..8e447873a7 100644 --- a/src/PhpWord/Element/Object.php +++ b/src/PhpWord/Element/OLEObject.php @@ -1,4 +1,5 @@ source = $source; $this->style = $this->setNewStyle(new ImageStyle(), $style, true); - $this->icon = realpath(__DIR__ . "/../resources/{$ext}.png"); + $this->icon = realpath(__DIR__ . "/../resources/{$pathInfoExtension}.png"); - return $this; - } else { - throw new InvalidObjectException(); + return; } + + throw new InvalidObjectException(); } /** - * Get object source + * Get object source. * * @return string */ @@ -99,9 +98,9 @@ public function getSource() } /** - * Get object style + * Get object style. * - * @return \PhpOffice\PhpWord\Style\Image + * @return ?ImageStyle */ public function getStyle() { @@ -109,7 +108,7 @@ public function getStyle() } /** - * Get object icon + * Get object icon. * * @return string */ @@ -119,7 +118,7 @@ public function getIcon() } /** - * Get image relation ID + * Get image relation ID. * * @return int */ @@ -132,34 +131,9 @@ public function getImageRelationId() * Set Image Relation ID. * * @param int $rId - * @return void */ - public function setImageRelationId($rId) + public function setImageRelationId($rId): void { $this->imageRelationId = $rId; } - - /** - * Get Object ID - * - * @return int - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getObjectId() - { - return $this->relationId + 1325353440; - } - - /** - * Set Object ID - * - * @param int $objId - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function setObjectId($objId) - { - $this->relationId = $objId; - } } diff --git a/src/PhpWord/Element/PageBreak.php b/src/PhpWord/Element/PageBreak.php index a1e7e998e8..950f0b7d5f 100644 --- a/src/PhpWord/Element/PageBreak.php +++ b/src/PhpWord/Element/PageBreak.php @@ -1,4 +1,5 @@ fontStyle = $this->setNewStyle(new Font('text'), $fontStyle); $this->paragraphStyle = $this->setNewStyle(new Paragraph(), $paragraphStyle); - $this->text = String::toUTF8($text); - $matches = preg_split('/({.*?})/', $this->text, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $this->text = SharedText::toUTF8($text); + $matches = preg_split('/({.*?})/', $this->text ?? '', -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); if (isset($matches[0])) { $this->text = $matches; } - - return $this; } /** - * Get Text style + * Get Text style. * - * @return string|\PhpOffice\PhpWord\Style\Font + * @return null|Font|string */ public function getFontStyle() { @@ -81,9 +78,9 @@ public function getFontStyle() } /** - * Get Paragraph style + * Get Paragraph style. * - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return null|Paragraph|string */ public function getParagraphStyle() { @@ -91,9 +88,9 @@ public function getParagraphStyle() } /** - * Get Text content + * Get Text content. * - * @return string + * @return null|array|string */ public function getText() { diff --git a/src/PhpWord/Element/Row.php b/src/PhpWord/Element/Row.php index e2df632551..af8c19dd84 100644 --- a/src/PhpWord/Element/Row.php +++ b/src/PhpWord/Element/Row.php @@ -1,4 +1,5 @@ baseTextRun = $baseTextRun; + $this->rubyTextRun = $rubyTextRun; + $this->properties = $properties; + } + + /** + * Get base text run. + */ + public function getBaseTextRun(): TextRun + { + return $this->baseTextRun; + } + + /** + * Set the base text run. + */ + public function setBaseTextRun(TextRun $textRun): self + { + $this->baseTextRun = $textRun; + + return $this; + } + + /** + * Get ruby text run. + */ + public function getRubyTextRun(): TextRun + { + return $this->rubyTextRun; + } + + /** + * Set the ruby text run. + */ + public function setRubyTextRun(TextRun $textRun): self + { + $this->rubyTextRun = $textRun; + + return $this; + } + + /** + * Get ruby properties. + */ + public function getProperties(): RubyProperties + { + return $this->properties; + } + + /** + * Set the ruby properties. + */ + public function setProperties(RubyProperties $properties): self + { + $this->properties = $properties; + + return $this; + } +} diff --git a/src/PhpWord/Element/SDT.php b/src/PhpWord/Element/SDT.php index c69ed42772..62a66c0426 100644 --- a/src/PhpWord/Element/SDT.php +++ b/src/PhpWord/Element/SDT.php @@ -1,4 +1,5 @@ setType($type); } /** - * Get type + * Get type. * * @return string */ @@ -69,23 +84,24 @@ public function getType() } /** - * Set type + * Set type. * * @param string $value + * * @return self */ public function setType($value) { - $enum = array('comboBox', 'dropDownList', 'date'); + $enum = ['plainText', 'comboBox', 'dropDownList', 'date']; $this->type = $this->setEnumVal($value, $enum, 'comboBox'); return $this; } /** - * Get value + * Get value. * - * @return string|bool|int + * @return null|bool|int|string */ public function getValue() { @@ -93,9 +109,10 @@ public function getValue() } /** - * Set value + * Set value. + * + * @param null|bool|int|string $value * - * @param string|bool|int $value * @return self */ public function setValue($value) @@ -106,7 +123,7 @@ public function setValue($value) } /** - * Get listItems + * Get listItems. * * @return array */ @@ -116,9 +133,10 @@ public function getListItems() } /** - * Set listItems + * Set listItems. * * @param array $value + * * @return self */ public function setListItems($value) @@ -127,4 +145,52 @@ public function setListItems($value) return $this; } + + /** + * Get tag. + * + * @return string + */ + public function getTag() + { + return $this->tag; + } + + /** + * Set tag. + * + * @param string $tag + * + * @return self + */ + public function setTag($tag) + { + $this->tag = $tag; + + return $this; + } + + /** + * Get alias. + * + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * Set alias. + * + * @param string $alias + * + * @return self + */ + public function setAlias($alias) + { + $this->alias = $alias; + + return $this; + } } diff --git a/src/PhpWord/Element/Section.php b/src/PhpWord/Element/Section.php index d746f69bb3..0ae00aa9f6 100644 --- a/src/PhpWord/Element/Section.php +++ b/src/PhpWord/Element/Section.php @@ -1,4 +1,5 @@ sectionId = $sectionCount; $this->setDocPart($this->container, $this->sectionId); - $this->style = new SectionStyle(); - $this->setStyle($style); + if (null === $style) { + $style = new SectionStyle(); + } + $this->style = $this->setNewStyle(new SectionStyle(), $style); } /** * Set section style. - * - * @param array $style - * @return void */ - public function setStyle($style = null) + public function setStyle(?array $style = null): void { - if (!is_null($style) && is_array($style)) { + if (null !== $style) { $this->style->setStyleByArray($style); } } /** - * Get section style + * Get section style. * - * @return \PhpOffice\PhpWord\Style\Section + * @return ?SectionStyle */ public function getStyle() { @@ -89,11 +94,13 @@ public function getStyle() } /** - * Add header + * Add header. + * + * @since 0.10.0 * * @param string $type + * * @return Header - * @since 0.10.0 */ public function addHeader($type = Header::AUTO) { @@ -101,11 +108,13 @@ public function addHeader($type = Header::AUTO) } /** - * Add footer + * Add footer. + * + * @since 0.10.0 * * @param string $type + * * @return Footer - * @since 0.10.0 */ public function addFooter($type = Header::AUTO) { @@ -113,7 +122,7 @@ public function addFooter($type = Header::AUTO) } /** - * Get header elements + * Get header elements. * * @return Header[] */ @@ -123,7 +132,7 @@ public function getHeaders() } /** - * Get footer elements + * Get footer elements. * * @return Footer[] */ @@ -132,13 +141,31 @@ public function getFooters() return $this->footers; } + /** + * Get the footnote properties. + * + * @return FootnoteProperties + */ + public function getFootnoteProperties() + { + return $this->footnoteProperties; + } + + /** + * Set the footnote properties. + */ + public function setFootnoteProperties(?FootnoteProperties $footnoteProperties = null): void + { + $this->footnoteProperties = $footnoteProperties; + } + /** * Is there a header for this section that is for the first page only? * * If any of the Header instances have a type of Header::FIRST then this method returns true. * False otherwise. * - * @return boolean + * @return bool */ public function hasDifferentFirstPage() { @@ -147,100 +174,43 @@ public function hasDifferentFirstPage() return true; } } + foreach ($this->footers as $footer) { + if ($footer->getType() == Header::FIRST) { + return true; + } + } + return false; } /** - * Add header/footer + * Add header/footer. * - * @param string $type - * @param boolean $header - * @return Header|Footer - * @throws \PhpOffice\PhpWord\Exception\Exception * @since 0.10.0 + * + * @param string $type + * @param bool $header + * + * @return Footer|Header */ private function addHeaderFooter($type = Header::AUTO, $header = true) { - $containerClass = substr(get_class($this), 0, strrpos(get_class($this), '\\')) . '\\' . + $containerClass = substr(static::class, 0, strrpos(static::class, '\\') ?: 0) . '\\' . ($header ? 'Header' : 'Footer'); $collectionArray = $header ? 'headers' : 'footers'; $collection = &$this->$collectionArray; - if (in_array($type, array(Header::AUTO, Header::FIRST, Header::EVEN))) { + if (in_array($type, [Header::AUTO, Header::FIRST, Header::EVEN])) { $index = count($collection); - /** @var \PhpOffice\PhpWord\Element\AbstractContainer $container Type hint */ + /** @var AbstractContainer $container Type hint */ $container = new $containerClass($this->sectionId, ++$index, $type); $container->setPhpWord($this->phpWord); $collection[$index] = $container; + return $container; - } else { - throw new Exception('Invalid header/footer type.'); } - } - - /** - * Set section style - * - * @param array $settings - * @deprecated 0.12.0 - * @codeCoverageIgnore - */ - public function setSettings($settings = null) - { - $this->setStyle($settings); - } - - /** - * Get section style - * - * @return \PhpOffice\PhpWord\Style\Section - * @deprecated 0.12.0 - * @codeCoverageIgnore - */ - public function getSettings() - { - return $this->getStyle(); - } - - /** - * Create header - * - * @return Header - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function createHeader() - { - return $this->addHeader(); - } - - /** - * Create footer - * - * @return Footer - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function createFooter() - { - return $this->addFooter(); - } - - /** - * Get footer - * - * @return Footer - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getFooter() - { - if (empty($this->footers)) { - return null; - } else { - return $this->footers[1]; - } + throw new Exception('Invalid header/footer type.'); } } diff --git a/src/PhpWord/Element/Shape.php b/src/PhpWord/Element/Shape.php index a7a96d1884..9ea221db3b 100644 --- a/src/PhpWord/Element/Shape.php +++ b/src/PhpWord/Element/Shape.php @@ -1,4 +1,5 @@ type = $this->setEnumVal($value, $enum, null); return $this; } /** - * Get shape style + * Get shape style. * - * @return \PhpOffice\PhpWord\Style\Shape + * @return ?ShapeStyle */ public function getStyle() { diff --git a/src/PhpWord/Element/TOC.php b/src/PhpWord/Element/TOC.php index a56d9ffe40..9e784c75df 100644 --- a/src/PhpWord/Element/TOC.php +++ b/src/PhpWord/Element/TOC.php @@ -1,4 +1,5 @@ TOCStyle = new TOCStyle(); + $this->tocStyle = new TOCStyle(); - if (!is_null($tocStyle) && is_array($tocStyle)) { - $this->TOCStyle->setStyleByArray($tocStyle); + if (null !== $tocStyle) { + $this->tocStyle->setStyleByArray($tocStyle); } - if (!is_null($fontStyle) && is_array($fontStyle)) { + if (null !== $fontStyle && is_array($fontStyle)) { $this->fontStyle = new Font(); $this->fontStyle->setStyleByArray($fontStyle); } else { @@ -83,19 +82,19 @@ public function __construct($fontStyle = null, $tocStyle = null, $minDepth = 1, } /** - * Get all titles + * Get all titles. * * @return array */ public function getTitles() { if (!$this->phpWord instanceof PhpWord) { - return array(); + return []; } $titles = $this->phpWord->getTitles()->getItems(); foreach ($titles as $i => $title) { - /** @var \PhpOffice\PhpWord\Element\Title $title Type hint */ + /** @var Title $title Type hint */ $depth = $title->getDepth(); if ($this->minDepth > $depth) { unset($titles[$i]); @@ -109,19 +108,19 @@ public function getTitles() } /** - * Get TOC Style + * Get TOC Style. * - * @return \PhpOffice\PhpWord\Style\TOC + * @return TOCStyle */ public function getStyleTOC() { - return $this->TOCStyle; + return $this->tocStyle; } /** - * Get Font Style + * Get Font Style. * - * @return \PhpOffice\PhpWord\Style\Font + * @return Font|string */ public function getStyleFont() { @@ -132,15 +131,14 @@ public function getStyleFont() * Set max depth. * * @param int $value - * @return void */ - public function setMaxDepth($value) + public function setMaxDepth($value): void { $this->maxDepth = $value; } /** - * Get Max Depth + * Get Max Depth. * * @return int Max depth of titles */ @@ -153,15 +151,14 @@ public function getMaxDepth() * Set min depth. * * @param int $value - * @return void */ - public function setMinDepth($value) + public function setMinDepth($value): void { $this->minDepth = $value; } /** - * Get Min Depth + * Get Min Depth. * * @return int Min depth of titles */ diff --git a/src/PhpWord/Element/Table.php b/src/PhpWord/Element/Table.php index 5f0b8f7953..7fb1030638 100644 --- a/src/PhpWord/Element/Table.php +++ b/src/PhpWord/Element/Table.php @@ -1,4 +1,5 @@ width = $width; } /** - * Get column count + * Get column count. * * @return int */ public function countColumns() { $columnCount = 0; - if (is_array($this->rows)) { - $rowCount = count($this->rows); - for ($i = 0; $i < $rowCount; $i++) { - /** @var \PhpOffice\PhpWord\Element\Row $row Type hint */ - $row = $this->rows[$i]; - $cellCount = count($row->getCells()); - if ($columnCount < $cellCount) { - $columnCount = $cellCount; - } + + $rowCount = count($this->rows); + for ($i = 0; $i < $rowCount; ++$i) { + /** @var Row $row Type hint */ + $row = $this->rows[$i]; + $cellCount = count($row->getCells()); + if ($columnCount < $cellCount) { + $columnCount = $cellCount; } } return $columnCount; } + + /** + * The first declared cell width for each column. + * + * @return int[] + */ + public function findFirstDefinedCellWidths() + { + $cellWidths = []; + + foreach ($this->rows as $row) { + $cells = $row->getCells(); + if (count($cells) <= count($cellWidths)) { + continue; + } + $cellWidths = []; + foreach ($cells as $cell) { + $cellWidths[] = $cell->getWidth(); + } + } + + return $cellWidths; + } } diff --git a/src/PhpWord/Element/Text.php b/src/PhpWord/Element/Text.php index 52fccb3f28..f20b273e02 100644 --- a/src/PhpWord/Element/Text.php +++ b/src/PhpWord/Element/Text.php @@ -1,4 +1,5 @@ paragraphStyle = new Paragraph; + $this->paragraphStyle = new Paragraph(); $this->paragraphStyle->setStyleByArray($style); } elseif ($style instanceof Paragraph) { $this->paragraphStyle = $style; } elseif (null === $style) { - $this->paragraphStyle = new Paragraph; + $this->paragraphStyle = new Paragraph(); } else { $this->paragraphStyle = $style; } @@ -119,9 +122,9 @@ public function setParagraphStyle($style = null) } /** - * Get Paragraph style + * Get Paragraph style. * - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return Paragraph|string */ public function getParagraphStyle() { @@ -129,24 +132,23 @@ public function getParagraphStyle() } /** - * Set text content + * Set text content. * * @param string $text + * * @return self */ public function setText($text) { - $this->text = String::toUTF8($text); + $this->text = SharedText::toUTF8($text); return $this; } /** - * Get Text content - * - * @return string + * Get Text content. */ - public function getText() + public function getText(): ?string { return $this->text; } diff --git a/src/PhpWord/Element/TextBox.php b/src/PhpWord/Element/TextBox.php index d929bf35db..b9f8b50bc5 100644 --- a/src/PhpWord/Element/TextBox.php +++ b/src/PhpWord/Element/TextBox.php @@ -1,4 +1,5 @@ setParagraphStyle($paragraphStyle); } - if (!is_null($fontStyle)) { + if (null !== $fontStyle) { $this->setFontStyle($fontStyle, $paragraphStyle); } } /** - * Set Text style + * Set Text style. * * @param mixed $style * @param mixed $paragraphStyle - * @return string|\PhpOffice\PhpWord\Style\Font + * + * @return Font|string */ public function setFontStyle($style = null, $paragraphStyle = null) { @@ -74,13 +76,14 @@ public function setFontStyle($style = null, $paragraphStyle = null) $this->fontStyle = $style; $this->setParagraphStyle($paragraphStyle); } + return $this->fontStyle; } /** - * Get Text style + * Get Text style. * - * @return string|\PhpOffice\PhpWord\Style\Font + * @return null|Font|string */ public function getFontStyle() { @@ -88,28 +91,30 @@ public function getFontStyle() } /** - * Set Paragraph style + * Set Paragraph style. + * + * @param array|Paragraph|string $style * - * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $style - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return Paragraph|string */ public function setParagraphStyle($style = null) { if (is_array($style)) { - $this->paragraphStyle = new Paragraph; + $this->paragraphStyle = new Paragraph(); $this->paragraphStyle->setStyleByArray($style); } elseif ($style instanceof Paragraph) { $this->paragraphStyle = $style; } else { $this->paragraphStyle = $style; } + return $this->paragraphStyle; } /** - * Get Paragraph style + * Get Paragraph style. * - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return null|Paragraph|string */ public function getParagraphStyle() { @@ -117,12 +122,12 @@ public function getParagraphStyle() } /** - * Has font/paragraph style defined + * Has font/paragraph style defined. * * @return bool */ public function hasStyle() { - return !is_null($this->fontStyle) || !is_null($this->paragraphStyle); + return null !== $this->fontStyle || null !== $this->paragraphStyle; } } diff --git a/src/PhpWord/Element/TextRun.php b/src/PhpWord/Element/TextRun.php index c356cfa8e1..0c9a2322a7 100644 --- a/src/PhpWord/Element/TextRun.php +++ b/src/PhpWord/Element/TextRun.php @@ -1,4 +1,5 @@ paragraphStyle = $this->setNewStyle(new Paragraph(), $paragraphStyle); + $this->paragraphStyle = $this->setParagraphStyle($paragraphStyle); } /** - * Get Paragraph style + * Get Paragraph style. * - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return Paragraph|string */ public function getParagraphStyle() { return $this->paragraphStyle; } + + /** + * Set Paragraph style. + * + * @param array|Paragraph|string $style + * + * @return Paragraph|string + */ + public function setParagraphStyle($style = null) + { + if (is_array($style)) { + $this->paragraphStyle = new Paragraph(); + $this->paragraphStyle->setStyleByArray($style); + } elseif ($style instanceof Paragraph) { + $this->paragraphStyle = $style; + } elseif (null === $style) { + $this->paragraphStyle = new Paragraph(); + } else { + $this->paragraphStyle = $style; + } + + return $this->paragraphStyle; + } + + public function getText(): string + { + $outstr = ''; + foreach ($this->getElements() as $element) { + if ($element instanceof Text) { + $outstr .= $element->getText(); + } elseif ($element instanceof Ruby) { + $outstr .= $element->getBaseTextRun()->getText() . + ' (' . $element->getRubyTextRun()->getText() . ')'; + } + } + + return $outstr; + } } diff --git a/src/PhpWord/Element/Title.php b/src/PhpWord/Element/Title.php index cf1d49c81a..89f438a5ef 100644 --- a/src/PhpWord/Element/Title.php +++ b/src/PhpWord/Element/Title.php @@ -1,4 +1,5 @@ text = String::toUTF8($text); + if (is_string($text)) { + $this->text = SharedText::toUTF8($text); + } elseif ($text instanceof TextRun) { + $this->text = $text; + } else { + throw new InvalidArgumentException('Invalid text, should be a string or a TextRun'); + } + $this->depth = $depth; - if (array_key_exists("Heading_{$this->depth}", Style::getStyles())) { - $this->style = "Heading{$this->depth}"; + $styleName = $depth === 0 ? 'Title' : "Heading_{$this->depth}"; + if (array_key_exists($styleName, Style::getStyles())) { + $this->style = str_replace('_', '', $styleName); } - return $this; + if ($pageNumber !== null) { + $this->pageNumber = $pageNumber; + } } /** - * Get Title Text content + * Get Title Text content. * - * @return string + * @return string|TextRun */ public function getText() { @@ -81,9 +100,9 @@ public function getText() } /** - * Get depth + * Get depth. * - * @return integer + * @return int */ public function getDepth() { @@ -91,12 +110,20 @@ public function getDepth() } /** - * Get Title style + * 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/Element/TrackChange.php b/src/PhpWord/Element/TrackChange.php new file mode 100644 index 0000000000..3539bdc627 --- /dev/null +++ b/src/PhpWord/Element/TrackChange.php @@ -0,0 +1,105 @@ +changeType = $changeType; + $this->author = $author; + if ($date !== null && $date !== false) { + $this->date = ($date instanceof DateTime) ? $date : new DateTime('@' . $date); + } + } + + /** + * Get TrackChange Author. + * + * @return string + */ + public function getAuthor() + { + return $this->author; + } + + /** + * Get TrackChange Date. + * + * @return DateTime + */ + public function getDate() + { + return $this->date; + } + + /** + * Get the Change type. + * + * @return string + */ + public function getChangeType() + { + return $this->changeType; + } +} diff --git a/tests/PhpWord/Tests/Shared/XMLWriterTest.php b/src/PhpWord/Escaper/AbstractEscaper.php similarity index 50% rename from tests/PhpWord/Tests/Shared/XMLWriterTest.php rename to src/PhpWord/Escaper/AbstractEscaper.php index 08db39184f..9c5dca39e0 100644 --- a/tests/PhpWord/Tests/Shared/XMLWriterTest.php +++ b/src/PhpWord/Escaper/AbstractEscaper.php @@ -1,4 +1,5 @@ foo(); + if (is_array($input)) { + foreach ($input as &$item) { + $item = $this->escapeSingleValue($item); + } + } else { + $input = $this->escapeSingleValue($input); + } + + return $input; } } diff --git a/src/PhpWord/Escaper/EscaperInterface.php b/src/PhpWord/Escaper/EscaperInterface.php new file mode 100644 index 0000000000..a2f7d425ad --- /dev/null +++ b/src/PhpWord/Escaper/EscaperInterface.php @@ -0,0 +1,34 @@ + $code || $code >= 0x80) { + return '{\\u' . $code . '}'; + } + if ($code == 123 || $code == 125 || $code == 92) { // open or close brace or backslash + return '\\' . chr($code); + } + + return chr($code); + } + + protected function escapeMultibyteCharacter($code) + { + return '\\uc0{\\u' . $code . '}'; + } + + /** + * @see http://www.randomchaos.com/documents/?source=php_and_unicode + * + * @param ?string $input + */ + protected function escapeSingleValue($input) + { + $escapedValue = ''; + + $numberOfBytes = 1; + $bytes = []; + for ($i = 0; $i < strlen($input); ++$i) { + $character = $input[$i]; + $asciiCode = ord($character); + + if ($asciiCode < 128) { + $escapedValue .= $this->escapeAsciiCharacter($asciiCode); + } else { + if (0 == count($bytes)) { + if ($asciiCode < 224) { + $numberOfBytes = 2; + } elseif ($asciiCode < 240) { + $numberOfBytes = 3; + } elseif ($asciiCode < 248) { + $numberOfBytes = 4; + } + } + + $bytes[] = $asciiCode; + + if ($numberOfBytes == count($bytes)) { + if (4 == $numberOfBytes) { + $multibyteCode = ($bytes[0] % 8) * 262144 + ($bytes[1] % 64) * 4096 + ($bytes[2] % 64) * 64 + ($bytes[3] % 64); + } elseif (3 == $numberOfBytes) { + $multibyteCode = ($bytes[0] % 16) * 4096 + ($bytes[1] % 64) * 64 + ($bytes[2] % 64); + } else { + $multibyteCode = ($bytes[0] % 32) * 64 + ($bytes[1] % 64); + } + + if (65279 != $multibyteCode) { + $escapedValue .= $multibyteCode < 128 ? $this->escapeAsciiCharacter($multibyteCode) : $this->escapeMultibyteCharacter($multibyteCode); + } + + $numberOfBytes = 1; + $bytes = []; + } + } + } + + return $escapedValue; + } +} diff --git a/src/PhpWord/Escaper/Xml.php b/src/PhpWord/Escaper/Xml.php new file mode 100644 index 0000000000..70e9cc21a4 --- /dev/null +++ b/src/PhpWord/Escaper/Xml.php @@ -0,0 +1,32 @@ +load($filename); } + /** - * Check if it's a concrete class (not abstract nor interface) + * 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 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). * * @param string $class + * * @return bool */ private static function isConcreteClass($class) { - $reflection = new \ReflectionClass($class); + $reflection = new ReflectionClass($class); + return !$reflection->isAbstract() && !$reflection->isInterface(); } } diff --git a/src/PhpWord/Media.php b/src/PhpWord/Media.php index 59735cea4c..0a340a0a3c 100644 --- a/src/PhpWord/Media.php +++ b/src/PhpWord/Media.php @@ -1,4 +1,5 @@ $mediaTypeCount); + $mediaData = ['mediaIndex' => $mediaTypeCount]; switch ($mediaType) { // Images case 'image': - if (is_null($image)) { + if (null === $image) { throw new Exception('Image object not assigned.'); } $isMemImage = $image->isMemImage(); @@ -73,22 +74,22 @@ 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); $image->setMediaIndex($mediaTypeCount); - break; - // Objects + break; + // Objects case 'object': $target = "{$container}_oleObject{$mediaTypeCount}.bin"; - break; - // Links + break; + // Links case 'link': $target = $source; + break; } @@ -97,23 +98,27 @@ public static function addElement($container, $mediaType, $source, Image $image $mediaData['type'] = $mediaType; $mediaData['rID'] = $rId; self::$elements[$container][$mediaId] = $mediaData; + return $rId; - } else { - $mediaData = self::$elements[$container][$mediaId]; - if (!is_null($image)) { - $image->setTarget($mediaData['target']); - $image->setMediaIndex($mediaData['mediaIndex']); - } - return $mediaData['rID']; } + + $mediaData = self::$elements[$container][$mediaId]; + if (null !== $image) { + $image->setTarget($mediaData['target']); + $image->setMediaIndex($mediaData['mediaIndex']); + } + + return $mediaData['rID']; } /** - * Get media elements count + * Get media elements count. * * @param string $container section|headerx|footerx|footnote|endnote * @param string $mediaType image|object|link - * @return integer + * + * @return int + * * @since 0.10.0 */ public static function countElements($container, $mediaType = null) @@ -122,12 +127,12 @@ public static function countElements($container, $mediaType = null) if (isset(self::$elements[$container])) { foreach (self::$elements[$container] as $mediaData) { - if (!is_null($mediaType)) { + if (null !== $mediaType) { if ($mediaType == $mediaData['type']) { - $mediaCount++; + ++$mediaCount; } } else { - $mediaCount++; + ++$mediaCount; } } } @@ -136,16 +141,18 @@ public static function countElements($container, $mediaType = null) } /** - * Get media elements + * Get media elements. * * @param string $container section|headerx|footerx|footnote|endnote * @param string $type image|object|link + * * @return array + * * @since 0.10.0 */ public static function getElements($container, $type = null) { - $elements = array(); + $elements = []; // If header/footer, search for headerx and footerx where x is number if ($container == 'header' || $container == 'footer') { @@ -154,26 +161,30 @@ public static function getElements($container, $type = null) $elements[$key] = $val; } } + return $elements; - } else { - if (!isset(self::$elements[$container])) { - return $elements; - } - return self::getElementsByType($container, $type); } + + if (!isset(self::$elements[$container])) { + return $elements; + } + + return self::getElementsByType($container, $type); } /** - * Get elements by media type + * Get elements by media type. * * @param string $container section|footnote|endnote * @param string $type image|object|link + * * @return array + * * @since 0.11.0 Splitted from `getElements` to reduce complexity */ private static function getElementsByType($container, $type = null) { - $elements = array(); + $elements = []; foreach (self::$elements[$container] as $key => $data) { if ($type !== null) { @@ -189,144 +200,10 @@ private static function getElementsByType($container, $type = null) } /** - * Reset media elements - */ - public static function resetElements() - { - self::$elements = array(); - } - - /** - * Add new Section Media Element - * - * @param string $src - * @param string $type - * @param \PhpOffice\PhpWord\Element\Image $image - * @return integer - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public static function addSectionMediaElement($src, $type, Image $image = null) - { - return self::addElement('section', $type, $src, $image); - } - - /** - * Add new Section Link Element - * - * @param string $linkSrc - * @return integer - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public static function addSectionLinkElement($linkSrc) - { - return self::addElement('section', 'link', $linkSrc); - } - - /** - * Get Section Media Elements - * - * @param string $key - * @return array - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public static function getSectionMediaElements($key = null) - { - return self::getElements('section', $key); - } - - /** - * Get Section Media Elements Count - * - * @param string $key - * @return integer - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public static function countSectionMediaElements($key = null) - { - return self::countElements('section', $key); - } - - /** - * Add new Header Media Element - * - * @param integer $headerCount - * @param string $src - * @param \PhpOffice\PhpWord\Element\Image $image - * @return integer - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public static function addHeaderMediaElement($headerCount, $src, Image $image = null) - { - return self::addElement("header{$headerCount}", 'image', $src, $image); - } - - /** - * Get Header Media Elements Count - * - * @param string $key - * @return integer - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public static function countHeaderMediaElements($key) - { - return self::countElements($key); - } - - /** - * Get Header Media Elements - * - * @return array - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public static function getHeaderMediaElements() - { - return self::getElements('header'); - } - - /** - * Add new Footer Media Element - * - * @param integer $footerCount - * @param string $src - * @param \PhpOffice\PhpWord\Element\Image $image - * @return integer - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public static function addFooterMediaElement($footerCount, $src, Image $image = null) - { - return self::addElement("footer{$footerCount}", 'image', $src, $image); - } - - /** - * Get Footer Media Elements Count - * - * @param string $key - * @return integer - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public static function countFooterMediaElements($key) - { - return self::countElements($key); - } - - /** - * Get Footer Media Elements - * - * @return array - * @deprecated 0.10.0 - * @codeCoverageIgnore + * Reset media elements. */ - public static function getFooterMediaElements() + public static function resetElements(): void { - return self::getElements('footer'); + self::$elements = []; } } diff --git a/src/PhpWord/Metadata/Compatibility.php b/src/PhpWord/Metadata/Compatibility.php index d78b97f1f9..b958d309b9 100644 --- a/src/PhpWord/Metadata/Compatibility.php +++ b/src/PhpWord/Metadata/Compatibility.php @@ -1,4 +1,5 @@ creator = ''; + $this->creator = ''; $this->lastModifiedBy = $this->creator; - $this->created = time(); - $this->modified = time(); - $this->title = ''; - $this->subject = ''; - $this->description = ''; - $this->keywords = ''; - $this->category = ''; - $this->company = ''; - $this->manager = ''; + $this->created = time(); + $this->modified = time(); + $this->title = ''; + $this->subject = ''; + $this->description = ''; + $this->keywords = ''; + $this->category = ''; + $this->company = ''; + $this->manager = ''; } /** - * Get Creator + * Get Creator. * * @return string */ @@ -143,9 +146,10 @@ public function getCreator() } /** - * Set Creator + * Set Creator. * * @param string $value + * * @return self */ public function setCreator($value = '') @@ -156,7 +160,7 @@ public function setCreator($value = '') } /** - * Get Last Modified By + * Get Last Modified By. * * @return string */ @@ -166,9 +170,10 @@ public function getLastModifiedBy() } /** - * Set Last Modified By + * Set Last Modified By. * * @param string $value + * * @return self */ public function setLastModifiedBy($value = '') @@ -179,7 +184,7 @@ public function setLastModifiedBy($value = '') } /** - * Get Created + * Get Created. * * @return int */ @@ -189,9 +194,10 @@ public function getCreated() } /** - * Set Created + * Set Created. * * @param int $value + * * @return self */ public function setCreated($value = null) @@ -202,7 +208,7 @@ public function setCreated($value = null) } /** - * Get Modified + * Get Modified. * * @return int */ @@ -212,9 +218,10 @@ public function getModified() } /** - * Set Modified + * Set Modified. * * @param int $value + * * @return self */ public function setModified($value = null) @@ -225,7 +232,7 @@ public function setModified($value = null) } /** - * Get Title + * Get Title. * * @return string */ @@ -235,9 +242,10 @@ public function getTitle() } /** - * Set Title + * Set Title. * * @param string $value + * * @return self */ public function setTitle($value = '') @@ -248,7 +256,7 @@ public function setTitle($value = '') } /** - * Get Description + * Get Description. * * @return string */ @@ -258,9 +266,10 @@ public function getDescription() } /** - * Set Description + * Set Description. * * @param string $value + * * @return self */ public function setDescription($value = '') @@ -271,7 +280,7 @@ public function setDescription($value = '') } /** - * Get Subject + * Get Subject. * * @return string */ @@ -281,9 +290,10 @@ public function getSubject() } /** - * Set Subject + * Set Subject. * * @param string $value + * * @return self */ public function setSubject($value = '') @@ -294,7 +304,7 @@ public function setSubject($value = '') } /** - * Get Keywords + * Get Keywords. * * @return string */ @@ -304,9 +314,10 @@ public function getKeywords() } /** - * Set Keywords + * Set Keywords. * * @param string $value + * * @return self */ public function setKeywords($value = '') @@ -317,7 +328,7 @@ public function setKeywords($value = '') } /** - * Get Category + * Get Category. * * @return string */ @@ -327,9 +338,10 @@ public function getCategory() } /** - * Set Category + * Set Category. * * @param string $value + * * @return self */ public function setCategory($value = '') @@ -340,7 +352,7 @@ public function setCategory($value = '') } /** - * Get Company + * Get Company. * * @return string */ @@ -350,9 +362,10 @@ public function getCompany() } /** - * Set Company + * Set Company. * * @param string $value + * * @return self */ public function setCompany($value = '') @@ -363,7 +376,7 @@ public function setCompany($value = '') } /** - * Get Manager + * Get Manager. * * @return string */ @@ -373,9 +386,10 @@ public function getManager() } /** - * Set Manager + * Set Manager. * * @param string $value + * * @return self */ public function setManager($value = '') @@ -386,7 +400,7 @@ public function setManager($value = '') } /** - * Get a List of Custom Property Names + * Get a List of Custom Property Names. * * @return array of string */ @@ -396,10 +410,11 @@ public function getCustomProperties() } /** - * Check if a Custom Property is defined + * Check if a Custom Property is defined. * * @param string $propertyName - * @return boolean + * + * @return bool */ public function isCustomPropertySet($propertyName) { @@ -407,37 +422,39 @@ public function isCustomPropertySet($propertyName) } /** - * Get a Custom Property Value + * Get a Custom Property Value. * * @param string $propertyName - * @return string + * + * @return mixed */ public function getCustomPropertyValue($propertyName) { if ($this->isCustomPropertySet($propertyName)) { return $this->customProperties[$propertyName]['value']; - } else { - return null; } + + return null; } /** - * Get a Custom Property Type + * Get a Custom Property Type. * * @param string $propertyName - * @return string + * + * @return ?string */ public function getCustomPropertyType($propertyName) { if ($this->isCustomPropertySet($propertyName)) { return $this->customProperties[$propertyName]['type']; - } else { - return null; } + + return null; } /** - * Set a Custom Property + * Set a Custom Property. * * @param string $propertyName * @param mixed $propertyValue @@ -447,17 +464,18 @@ public function getCustomPropertyType($propertyName) * 's': String * 'd': Date/Time * 'b': Boolean + * * @return self */ public function setCustomProperty($propertyName, $propertyValue = '', $propertyType = null) { - $propertyTypes = array( + $propertyTypes = [ self::PROPERTY_TYPE_INTEGER, self::PROPERTY_TYPE_FLOAT, self::PROPERTY_TYPE_STRING, self::PROPERTY_TYPE_DATE, - self::PROPERTY_TYPE_BOOLEAN - ); + self::PROPERTY_TYPE_BOOLEAN, + ]; if (($propertyType === null) || (!in_array($propertyType, $propertyTypes))) { if ($propertyValue === null) { $propertyType = self::PROPERTY_TYPE_STRING; @@ -467,23 +485,27 @@ public function setCustomProperty($propertyName, $propertyValue = '', $propertyT $propertyType = self::PROPERTY_TYPE_INTEGER; } elseif (is_bool($propertyValue)) { $propertyType = self::PROPERTY_TYPE_BOOLEAN; + } elseif ($propertyValue instanceof DateTime) { + $propertyType = self::PROPERTY_TYPE_DATE; } else { $propertyType = self::PROPERTY_TYPE_STRING; } } - $this->customProperties[$propertyName] = array( + $this->customProperties[$propertyName] = [ 'value' => $propertyValue, - 'type' => $propertyType - ); + 'type' => $propertyType, + ]; + return $this; } /** - * Convert document property based on type + * Convert document property based on type. * * @param string $propertyValue * @param string $propertyType + * * @return mixed */ public static function convertProperty($propertyValue, $propertyType) @@ -504,27 +526,28 @@ public static function convertProperty($propertyValue, $propertyType) case 'date': // Date return strtotime($propertyValue); case 'bool': // Boolean - return ($propertyValue == 'true') ? true : false; + return $propertyValue == 'true'; } return $propertyValue; } /** - * Convert document property type + * Convert document property type. * * @param string $propertyType + * * @return string */ public static function convertPropertyType($propertyType) { - $typeGroups = array( - self::PROPERTY_TYPE_INTEGER => array('i1', 'i2', 'i4', 'i8', 'int', 'ui1', 'ui2', 'ui4', 'ui8', 'uint'), - self::PROPERTY_TYPE_FLOAT => array('r4', 'r8', 'decimal'), - self::PROPERTY_TYPE_STRING => array('empty', 'null', 'lpstr', 'lpwstr', 'bstr'), - self::PROPERTY_TYPE_DATE => array('date', 'filetime'), - self::PROPERTY_TYPE_BOOLEAN => array('bool'), - ); + $typeGroups = [ + self::PROPERTY_TYPE_INTEGER => ['i1', 'i2', 'i4', 'i8', 'int', 'ui1', 'ui2', 'ui4', 'ui8', 'uint'], + self::PROPERTY_TYPE_FLOAT => ['r4', 'r8', 'decimal'], + self::PROPERTY_TYPE_STRING => ['empty', 'null', 'lpstr', 'lpwstr', 'bstr'], + self::PROPERTY_TYPE_DATE => ['date', 'filetime'], + self::PROPERTY_TYPE_BOOLEAN => ['bool'], + ]; foreach ($typeGroups as $groupId => $groupMembers) { if (in_array($propertyType, $groupMembers)) { return $groupId; @@ -535,10 +558,11 @@ public static function convertPropertyType($propertyType) } /** - * Set default for null and empty value + * Set default for null and empty value. * * @param mixed $value * @param mixed $default + * * @return mixed */ private function setValue($value, $default) @@ -551,22 +575,23 @@ private function setValue($value, $default) } /** - * Get conversion model depending on property type + * Get conversion model depending on property type. * * @param string $propertyType + * * @return string */ private static function getConversion($propertyType) { - $conversions = array( - 'empty' => array('empty'), - 'null' => array('null'), - 'int' => array('i1', 'i2', 'i4', 'i8', 'int'), - 'uint' => array('ui1', 'ui2', 'ui4', 'ui8', 'uint'), - 'float' => array('r4', 'r8', 'decimal'), - 'bool' => array('bool'), - 'date' => array('date', 'filetime'), - ); + $conversions = [ + 'empty' => ['empty'], + 'null' => ['null'], + 'int' => ['i1', 'i2', 'i4', 'i8', 'int'], + 'uint' => ['ui1', 'ui2', 'ui4', 'ui8', 'uint'], + 'float' => ['r4', 'r8', 'decimal'], + 'bool' => ['bool'], + 'date' => ['date', 'filetime'], + ]; foreach ($conversions as $conversion => $types) { if (in_array($propertyType, $types)) { return $conversion; diff --git a/src/PhpWord/Metadata/Protection.php b/src/PhpWord/Metadata/Protection.php index 3556ce8c2c..667b1438e8 100644 --- a/src/PhpWord/Metadata/Protection.php +++ b/src/PhpWord/Metadata/Protection.php @@ -1,4 +1,5 @@ setEditing($editing); + if ($editing != null) { + $this->setEditing($editing); + } } /** - * Get editing protection + * Get editing protection. * * @return string */ @@ -55,15 +90,117 @@ public function getEditing() } /** - * Set editing protection + * Set editing protection. + * + * @param string $editing Any value of \PhpOffice\PhpWord\SimpleType\DocProtect * - * @param string $editing * @return self */ public function setEditing($editing = null) { + DocProtect::validate($editing); $this->editing = $editing; return $this; } + + /** + * Get password. + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Set password. + * + * @param string $password + * + * @return self + */ + public function setPassword($password) + { + $this->password = $password; + + return $this; + } + + /** + * Get count for hash iterations. + * + * @return int + */ + public function getSpinCount() + { + return $this->spinCount; + } + + /** + * Set count for hash iterations. + * + * @param int $spinCount + * + * @return self + */ + public function setSpinCount($spinCount) + { + $this->spinCount = $spinCount; + + return $this; + } + + /** + * Get algorithm. + * + * @return string + */ + public function getAlgorithm() + { + return $this->algorithm; + } + + /** + * Set algorithm. + * + * @param string $algorithm + * + * @return self + */ + public function setAlgorithm($algorithm) + { + $this->algorithm = $algorithm; + + return $this; + } + + /** + * Get salt. + * + * @return string + */ + public function getSalt() + { + return $this->salt; + } + + /** + * Set salt. Salt HAS to be 16 characters, or an exception will be thrown. + * + * @param string $salt + * + * @return self + */ + public function setSalt($salt) + { + if ($salt !== null && strlen($salt) !== 16) { + throw new InvalidArgumentException('salt has to be of exactly 16 bytes length'); + } + + $this->salt = $salt; + + return $this; + } } diff --git a/src/PhpWord/Metadata/Settings.php b/src/PhpWord/Metadata/Settings.php new file mode 100644 index 0000000000..0e30ec76d6 --- /dev/null +++ b/src/PhpWord/Metadata/Settings.php @@ -0,0 +1,500 @@ +documentProtection == null) { + $this->documentProtection = new Protection(); + } + + return $this->documentProtection; + } + + /** + * @param Protection $documentProtection + */ + public function setDocumentProtection($documentProtection): void + { + $this->documentProtection = $documentProtection; + } + + /** + * @return ProofState + */ + public function getProofState() + { + if ($this->proofState == null) { + $this->proofState = new ProofState(); + } + + return $this->proofState; + } + + /** + * @param ProofState $proofState + */ + public function setProofState($proofState): void + { + $this->proofState = $proofState; + } + + /** + * Are spelling errors hidden. + * + * @return bool + */ + public function hasHideSpellingErrors() + { + return $this->hideSpellingErrors; + } + + /** + * Hide spelling errors. + * + * @param ?bool $hideSpellingErrors + */ + public function setHideSpellingErrors($hideSpellingErrors): void + { + $this->hideSpellingErrors = $hideSpellingErrors === null ? true : $hideSpellingErrors; + } + + /** + * Are grammatical errors hidden. + * + * @return bool + */ + public function hasHideGrammaticalErrors() + { + return $this->hideGrammaticalErrors; + } + + /** + * Hide grammatical errors. + * + * @param ?bool $hideGrammaticalErrors + */ + public function setHideGrammaticalErrors($hideGrammaticalErrors): void + { + $this->hideGrammaticalErrors = $hideGrammaticalErrors === null ? true : $hideGrammaticalErrors; + } + + /** + * @return bool + */ + public function hasEvenAndOddHeaders() + { + return $this->evenAndOddHeaders; + } + + /** + * @param ?bool $evenAndOddHeaders + */ + public function setEvenAndOddHeaders($evenAndOddHeaders): void + { + $this->evenAndOddHeaders = $evenAndOddHeaders === null ? true : $evenAndOddHeaders; + } + + /** + * Get the Visibility of Annotation Types. + * + * @return TrackChangesView + */ + public function getRevisionView() + { + return $this->revisionView; + } + + /** + * Set the Visibility of Annotation Types. + */ + public function setRevisionView(?TrackChangesView $trackChangesView = null): void + { + $this->revisionView = $trackChangesView; + } + + /** + * @return bool + */ + public function hasTrackRevisions() + { + return $this->trackRevisions; + } + + /** + * @param ?bool $trackRevisions + */ + public function setTrackRevisions($trackRevisions): void + { + $this->trackRevisions = $trackRevisions === null ? true : $trackRevisions; + } + + /** + * @return bool + */ + public function hasDoNotTrackMoves() + { + return $this->doNotTrackMoves; + } + + /** + * @param ?bool $doNotTrackMoves + */ + public function setDoNotTrackMoves($doNotTrackMoves): void + { + $this->doNotTrackMoves = $doNotTrackMoves === null ? true : $doNotTrackMoves; + } + + /** + * @return bool + */ + public function hasDoNotTrackFormatting() + { + return $this->doNotTrackFormatting; + } + + /** + * @param ?bool $doNotTrackFormatting + */ + public function setDoNotTrackFormatting($doNotTrackFormatting): void + { + $this->doNotTrackFormatting = $doNotTrackFormatting === null ? true : $doNotTrackFormatting; + } + + /** + * @return mixed + */ + public function getZoom() + { + return $this->zoom; + } + + /** + * @param mixed $zoom + */ + public function setZoom($zoom): void + { + if (is_numeric($zoom)) { + // zoom is a percentage + $this->zoom = $zoom; + } else { + Zoom::validate($zoom); + $this->zoom = $zoom; + } + } + + /** + * @return bool + */ + public function hasMirrorMargins() + { + return $this->mirrorMargins; + } + + /** + * @param bool $mirrorMargins + */ + public function setMirrorMargins($mirrorMargins): void + { + $this->mirrorMargins = $mirrorMargins; + } + + /** + * Returns the Language. + */ + public function getThemeFontLang(): ?Language + { + return $this->themeFontLang; + } + + /** + * Sets the Language for this document. + */ + public function setThemeFontLang(Language $themeFontLang): self + { + $this->themeFontLang = $themeFontLang; + + return $this; + } + + /** + * @return bool + */ + public function hasUpdateFields() + { + return $this->updateFields; + } + + /** + * @param ?bool $updateFields + */ + public function setUpdateFields($updateFields): void + { + $this->updateFields = $updateFields === null ? false : $updateFields; + } + + /** + * Returns the Radix Point for Field Code Evaluation. + * + * @return string + */ + public function getDecimalSymbol() + { + return $this->decimalSymbol; + } + + /** + * sets the Radix Point for Field Code Evaluation. + * + * @param string $decimalSymbol + */ + public function setDecimalSymbol($decimalSymbol): void + { + $this->decimalSymbol = $decimalSymbol; + } + + /** + * @return null|bool + */ + public function hasAutoHyphenation() + { + return $this->autoHyphenation; + } + + /** + * @param bool $autoHyphenation + */ + public function setAutoHyphenation($autoHyphenation): void + { + $this->autoHyphenation = (bool) $autoHyphenation; + } + + /** + * @return null|int + */ + public function getConsecutiveHyphenLimit() + { + return $this->consecutiveHyphenLimit; + } + + /** + * @param int $consecutiveHyphenLimit + */ + public function setConsecutiveHyphenLimit($consecutiveHyphenLimit): void + { + $this->consecutiveHyphenLimit = (int) $consecutiveHyphenLimit; + } + + /** + * @return null|float|int + */ + public function getHyphenationZone() + { + return $this->hyphenationZone; + } + + /** + * @param null|float|int $hyphenationZone Measurement unit is twip + */ + public function setHyphenationZone($hyphenationZone): void + { + $this->hyphenationZone = $hyphenationZone; + } + + /** + * @return null|bool + */ + public function hasDoNotHyphenateCaps() + { + return $this->doNotHyphenateCaps; + } + + /** + * @param bool $doNotHyphenateCaps + */ + 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 99c95c7912..c3200fe857 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -1,4 +1,5 @@ collections[$collection] = new $class(); } // Metadata - $metadata = array('DocInfo', 'Protection', 'Compatibility'); + $metadata = ['DocInfo', 'Settings', 'Compatibility']; foreach ($metadata as $meta) { $class = 'PhpOffice\\PhpWord\\Metadata\\' . $meta; $this->metadata[$meta] = new $class(); @@ -98,29 +96,30 @@ public function __construct() } /** - * Dynamic function call to reduce static dependency + * Dynamic function call to reduce static dependency. + * + * @since 0.12.0 * * @param mixed $function * @param mixed $args - * @throws \BadMethodCallException + * * @return mixed - * @since 0.12.0 */ public function __call($function, $args) { $function = strtolower($function); - $getCollection = array(); - $addCollection = array(); - $addStyle = array(); + $getCollection = []; + $addCollection = []; + $addStyle = []; - $collections = array('Bookmark', 'Title', 'Footnote', 'Endnote', 'Chart'); + $collections = ['Bookmark', 'Title', 'Footnote', 'Endnote', 'Chart', 'Comment']; foreach ($collections as $collection) { $getCollection[] = strtolower("get{$collection}s"); $addCollection[] = strtolower("add{$collection}"); } - $styles = array('Paragraph', 'Font', 'Table', 'Numbering', 'Link', 'Title'); + $styles = ['Paragraph', 'Font', 'Table', 'Numbering', 'Link', 'Title']; foreach ($styles as $style) { $addStyle[] = strtolower("add{$style}Style"); } @@ -136,25 +135,24 @@ 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(isset($args[0]) ? $args[0] : null); + return $collectionObject->addItem($args[0] ?? null); } // Run add style method if (in_array($function, $addStyle)) { - return forward_static_call_array(array('PhpOffice\\PhpWord\\Style', $function), $args); + return forward_static_call_array(['PhpOffice\\PhpWord\\Style', $function], $args); } // Exception - throw new \BadMethodCallException("Method $function is not defined."); + throw new BadMethodCallException("Method $function is not defined."); } /** - * Get document properties object + * Get document properties object. * - * @return \PhpOffice\PhpWord\Metadata\DocInfo + * @return Metadata\DocInfo */ public function getDocInfo() { @@ -162,31 +160,33 @@ public function getDocInfo() } /** - * Get protection + * Get compatibility. + * + * @return Metadata\Compatibility * - * @return \PhpOffice\PhpWord\Metadata\Protection * @since 0.12.0 */ - public function getProtection() + public function getCompatibility() { - return $this->metadata['Protection']; + return $this->metadata['Compatibility']; } /** - * Get compatibility + * Get compatibility. * - * @return \PhpOffice\PhpWord\Metadata\Compatibility - * @since 0.12.0 + * @return Metadata\Settings + * + * @since 0.14.0 */ - public function getCompatibility() + public function getSettings() { - return $this->metadata['Compatibility']; + return $this->metadata['Settings']; } /** - * Get all sections + * Get all sections. * - * @return \PhpOffice\PhpWord\Element\Section[] + * @return Section[] */ public function getSections() { @@ -194,10 +194,27 @@ public function getSections() } /** - * Create new section + * Returns the section at the requested position. + * + * @param int $index * - * @param array $style - * @return \PhpOffice\PhpWord\Element\Section + * @return null|Section + */ + public function getSection($index) + { + if (array_key_exists($index, $this->sections)) { + return $this->sections[$index]; + } + + return null; + } + + /** + * Create new section. + * + * @param null|array|string $style + * + * @return Section */ public function addSection($style = null) { @@ -209,7 +226,19 @@ public function addSection($style = null) } /** - * Get default font name + * Sorts the sections using the callable passed. + * + * @see http://php.net/manual/en/function.usort.php for usage + * + * @param callable $sorter + */ + public function sortSections($sorter): void + { + usort($this->sections, $sorter); + } + + /** + * Get default font name. * * @return string */ @@ -222,17 +251,50 @@ public function getDefaultFontName() * Set default font name. * * @param string $fontName - * @return void */ - public function setDefaultFontName($fontName) + public function setDefaultFontName($fontName): void { Settings::setDefaultFontName($fontName); } /** - * Get default font size + * Get default asian font name. + */ + public function getDefaultAsianFontName(): string + { + return Settings::getDefaultAsianFontName(); + } + + /** + * Set default asian font name. + * + * @param string $fontName + */ + public function setDefaultAsianFontName($fontName): void + { + Settings::setDefaultAsianFontName($fontName); + } + + /** + * Set default font color. + */ + public function setDefaultFontColor(string $fontColor): void + { + Settings::setDefaultFontColor($fontColor); + } + + /** + * Get default font color. + */ + public function getDefaultFontColor(): string + { + return Settings::getDefaultFontColor(); + } + + /** + * Get default font size. * - * @return integer + * @return int */ public function getDefaultFontSize() { @@ -243,18 +305,18 @@ public function getDefaultFontSize() * Set default font size. * * @param int $fontSize - * @return void */ - public function setDefaultFontSize($fontSize) + public function setDefaultFontSize($fontSize): void { Settings::setDefaultFontSize($fontSize); } /** - * Set default paragraph style definition to styles.xml + * Set default paragraph style definition to styles.xml. * * @param array $styles Paragraph style definition - * @return \PhpOffice\PhpWord\Style\Paragraph + * + * @return Style\Paragraph */ public function setDefaultParagraphStyle($styles) { @@ -262,47 +324,30 @@ public function setDefaultParagraphStyle($styles) } /** - * Load template by filename - * - * @deprecated 0.12.0 Use `new TemplateProcessor($documentTemplate)` instead. - * - * @param string $filename Fully qualified filename. - * @return TemplateProcessor - * @throws \PhpOffice\PhpWord\Exception\Exception - */ - public function loadTemplate($filename) - { - if (file_exists($filename)) { - return new TemplateProcessor($filename); - } else { - throw new Exception("Template file {$filename} not found."); - } - } - - /** - * Save to file or download + * Save to file or download. * * All exceptions should already been handled by the writers * * @param string $filename * @param string $format * @param bool $download + * * @return bool */ public function save($filename, $format = 'Word2007', $download = false) { - $mime = array( - 'Word2007' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'ODText' => 'application/vnd.oasis.opendocument.text', - 'RTF' => 'application/rtf', - 'HTML' => 'text/html', - 'PDF' => 'application/pdf', - ); + $mime = [ + 'Word2007' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'ODText' => 'application/vnd.oasis.opendocument.text', + 'RTF' => 'application/rtf', + 'HTML' => 'text/html', + 'PDF' => 'application/pdf', + ]; $writer = IOFactory::createWriter($this, $format); if ($download === true) { - header("Content-Description: File Transfer"); + header('Content-Description: File Transfer'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Type: ' . $mime[$format]); header('Content-Transfer-Encoding: binary'); @@ -317,11 +362,14 @@ public function save($filename, $format = 'Word2007', $download = false) } /** - * Create new section + * Create new section. * - * @param array $settings - * @return \PhpOffice\PhpWord\Element\Section * @deprecated 0.10.0 + * + * @param array $settings + * + * @return Section + * * @codeCoverageIgnore */ public function createSection($settings = null) @@ -330,10 +378,12 @@ public function createSection($settings = null) } /** - * Get document properties object + * Get document properties object. * - * @return \PhpOffice\PhpWord\Metadata\DocInfo * @deprecated 0.12.0 + * + * @return Metadata\DocInfo + * * @codeCoverageIgnore */ public function getDocumentProperties() @@ -342,11 +392,14 @@ public function getDocumentProperties() } /** - * Set document properties object + * Set document properties object. * - * @param \PhpOffice\PhpWord\Metadata\DocInfo $documentProperties - * @return self * @deprecated 0.12.0 + * + * @param Metadata\DocInfo $documentProperties + * + * @return self + * * @codeCoverageIgnore */ public function setDocumentProperties($documentProperties) diff --git a/src/PhpWord/Reader/AbstractReader.php b/src/PhpWord/Reader/AbstractReader.php index c08914e5c6..3c70834d16 100644 --- a/src/PhpWord/Reader/AbstractReader.php +++ b/src/PhpWord/Reader/AbstractReader.php @@ -1,4 +1,5 @@ readDataOnly = $value; + + return $this; + } + + public function hasImageLoading(): bool + { + return $this->imageLoading; + } + + public function setImageLoading(bool $value): self + { + $this->imageLoading = $value; + return $this; } /** - * Open file for reading + * Open file for reading. * * @param string $filename + * * @return resource - * @throws \PhpOffice\PhpWord\Exception\Exception */ protected function openFile($filename) { // Check if file exists if (!file_exists($filename) || !is_readable($filename)) { - throw new Exception("Could not open " . $filename . " for reading! File does not exist."); + throw new Exception("Could not open $filename for reading! File does not exist."); } // Open file - $this->fileHandle = fopen($filename, 'r'); + $this->fileHandle = fopen($filename, 'rb'); if ($this->fileHandle === false) { - throw new Exception("Could not open file " . $filename . " for reading."); + throw new Exception("Could not open file $filename for reading."); } } @@ -89,6 +112,7 @@ protected function openFile($filename) * Can the current ReaderInterface read the file? * * @param string $filename + * * @return bool */ public function canRead($filename) @@ -105,15 +129,4 @@ public function canRead($filename) return true; } - - /** - * Read data only? - * - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getReadDataOnly() - { - return $this->isReadDataOnly(); - } } diff --git a/src/PhpWord/Reader/HTML.php b/src/PhpWord/Reader/HTML.php index a6582a3f26..17871c21a2 100644 --- a/src/PhpWord/Reader/HTML.php +++ b/src/PhpWord/Reader/HTML.php @@ -1,4 +1,5 @@ addSection(); HTMLParser::addHtml($section, file_get_contents($docFile), true); } else { - throw new \Exception("Cannot read {$docFile}."); + throw new Exception("Cannot read {$docFile}."); } return $phpWord; diff --git a/src/PhpWord/Reader/MsDoc.php b/src/PhpWord/Reader/MsDoc.php index c63d8d9e22..7cbeb1a8ac 100644 --- a/src/PhpWord/Reader/MsDoc.php +++ b/src/PhpWord/Reader/MsDoc.php @@ -1,10 +1,19 @@ 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->documentSummaryInformation); + $this->documentSummaryInformation = $ole->getStream($ole->docSummaryInfos); } private function getNumInLcb($lcb, $iSize) @@ -151,18 +169,20 @@ private function getNumInLcb($lcb, $iSize) private function getArrayCP($data, $posMem, $iNum) { - $arrayCP = array(); - for ($inc = 0; $inc < $iNum; $inc++) { + $arrayCP = []; + for ($inc = 0; $inc < $iNum; ++$inc) { $arrayCP[$inc] = self::getInt4d($data, $posMem); $posMem += 4; } + return $arrayCP; } /** + * @see http://msdn.microsoft.com/en-us/library/dd949344%28v=office.12%29.aspx + * @see https://igor.io/2012/09/24/binary-parsing.html * - * @link http://msdn.microsoft.com/en-us/library/dd949344%28v=office.12%29.aspx - * @link https://igor.io/2012/09/24/binary-parsing.html + * @param string $data */ private function readFib($data) { @@ -199,7 +219,7 @@ private function readFib($data) // lKey $pos += 4; // envr - $pos += 1; + ++$pos; // $mem = self::getInt1d($data, $pos); // $fMac = ($mem >> 7) & 1; @@ -208,7 +228,7 @@ private function readFib($data) // $reserved1 = ($mem >> 4) & 1; // $reserved2 = ($mem >> 3) & 1; // $fSpare0 = ($mem >> 0) & bindec('111'); - $pos += 1; + ++$pos; // reserved3 $pos += 2; @@ -308,21 +328,25 @@ private function readFib($data) switch ($cbRgFcLcb) { case 0x005D: $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); + break; case 0x006C: $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2000); + break; case 0x0088: $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2000); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2002); + break; case 0x00A4: $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2000); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2002); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2003); + break; case 0x00B7: $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); @@ -330,6 +354,7 @@ private function readFib($data) $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2002); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2003); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2007); + break; } //----- cswNew @@ -1087,10 +1112,11 @@ private function readBlockFibRgFcLcb($data, $pos, $version) $this->arrayFib['lcbColorSchemeMapping'] = self::getInt4d($data, $pos); $pos += 4; } + return $pos; } - private function readFibContent() + private function readFibContent(): void { // Informations about Font $this->readRecordSttbfFfn(); @@ -1099,36 +1125,37 @@ private function readFibContent() $this->readRecordPlcfSed(); // reading paragraphs - //@link https://github.com/notmasteryet/CompoundFile/blob/ec118f354efebdee9102e41b5b7084fce81125b0/WordFileReader/WordDocument.cs#L86 + //@see https://github.com/notmasteryet/CompoundFile/blob/ec118f354efebdee9102e41b5b7084fce81125b0/WordFileReader/WordDocument.cs#L86 $this->readRecordPlcfBtePapx(); // reading character formattings - //@link https://github.com/notmasteryet/CompoundFile/blob/ec118f354efebdee9102e41b5b7084fce81125b0/WordFileReader/WordDocument.cs#L94 + //@see https://github.com/notmasteryet/CompoundFile/blob/ec118f354efebdee9102e41b5b7084fce81125b0/WordFileReader/WordDocument.cs#L94 $this->readRecordPlcfBteChpx(); $this->generatePhpWord(); } /** - * Section and information about them - * @link : http://msdn.microsoft.com/en-us/library/dd924458%28v=office.12%29.aspx + * Section and information about them. + * + * @see http://msdn.microsoft.com/en-us/library/dd924458%28v=office.12%29.aspx */ - private function readRecordPlcfSed() + private function readRecordPlcfSed(): void { $posMem = $this->arrayFib['fcPlcfSed']; // PlcfSed // PlcfSed : aCP - $aCP = array(); + $aCP = []; $aCP[0] = self::getInt4d($this->data1Table, $posMem); $posMem += 4; $aCP[1] = self::getInt4d($this->data1Table, $posMem); $posMem += 4; // PlcfSed : aSed - //@link : 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 = array(); + $aSed = []; for ($iInc = 0; $iInc < $numSed; ++$iInc) { // Sed : http://msdn.microsoft.com/en-us/library/dd950982%28v=office.12%29.aspx // fn @@ -1155,10 +1182,11 @@ private function readRecordPlcfSed() } /** - * Specifies the fonts that are used in the document - * @link : http://msdn.microsoft.com/en-us/library/dd943880%28v=office.12%29.aspx + * Specifies the fonts that are used in the document. + * + * @see http://msdn.microsoft.com/en-us/library/dd943880%28v=office.12%29.aspx */ - private function readRecordSttbfFfn() + private function readRecordSttbfFfn(): void { $posMem = $this->arrayFib['fcSttbfFfn']; @@ -1168,18 +1196,18 @@ private function readRecordSttbfFfn() $posMem += 2; if ($cData < 0x7FF0 && $cbExtra == 0) { - for ($inc = 0; $inc < $cData; $inc++) { + for ($inc = 0; $inc < $cData; ++$inc) { // len - $posMem += 1; + ++$posMem; // ffid - $posMem += 1; + ++$posMem; // wWeight (400 : Normal - 700 bold) $posMem += 2; // chs - $posMem += 1; + ++$posMem; // ixchSzAlt $ixchSzAlt = self::getInt1d($this->data1Table, $posMem); - $posMem += 1; + ++$posMem; // panose $posMem += 10; // fs @@ -1205,19 +1233,20 @@ private function readRecordSttbfFfn() $xszAlt .= chr($char); } while ($char != 0); } - $this->arrayFonts[] = array( + $this->arrayFonts[] = [ 'main' => $xszFfn, 'alt' => $xszAlt, - ); + ]; } } } /** - * Paragraph and information about them - * @link http://msdn.microsoft.com/en-us/library/dd908569%28v=office.12%29.aspx + * Paragraph and information about them. + * + * @see http://msdn.microsoft.com/en-us/library/dd908569%28v=office.12%29.aspx */ - private function readRecordPlcfBtePapx() + private function readRecordPlcfBtePapx(): void { $posMem = $this->arrayFib['fcPlcfBtePapx']; $num = $this->getNumInLcb($this->arrayFib['lcbPlcfBtePapx'], 4); @@ -1232,16 +1261,16 @@ private function readRecordPlcfBtePapx() $string = ''; $numRun = self::getInt1d($this->dataWorkDocument, $offset + 511); - $arrayRGFC = array(); - for ($inc = 0; $inc <= $numRun; $inc++) { + $arrayRGFC = []; + for ($inc = 0; $inc <= $numRun; ++$inc) { $arrayRGFC[$inc] = self::getInt4d($this->dataWorkDocument, $offset); $offset += 4; } - $arrayRGB = array(); - for ($inc = 1; $inc <= $numRun; $inc++) { - // @link http://msdn.microsoft.com/en-us/library/dd925804(v=office.12).aspx + $arrayRGB = []; + for ($inc = 1; $inc <= $numRun; ++$inc) { + // @see http://msdn.microsoft.com/en-us/library/dd925804(v=office.12).aspx $arrayRGB[$inc] = self::getInt1d($this->dataWorkDocument, $offset); - $offset += 1; + ++$offset; // reserved $offset += 12; } @@ -1251,10 +1280,12 @@ private function readRecordPlcfBtePapx() 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; } } } @@ -1295,7 +1326,7 @@ private function readRecordPlcfBtePapx() print_r('$sprm.ispmd : 0x'.dechex($sprm_IsPmd).PHP_EOL); print_r('$sprm.f : 0x'.dechex($sprm_F).PHP_EOL); print_r('$sprm.sgc : 0x'.dechex($sprm_Sgc)); - switch(dechex($sprm_Sgc)) { + switch (dechex($sprm_Sgc)) { case 0x01: print_r(' (Paragraph property)'); break; @@ -1314,12 +1345,12 @@ private function readRecordPlcfBtePapx() } print_r(PHP_EOL); print_r('$sprm.spra : 0x'.dechex($sprm_Spra).PHP_EOL); - switch(dechex($sprm_Spra)) { + switch (dechex($sprm_Spra)) { case 0x0: $operand = self::getInt1d($this->dataWorkDocument, $offset); $offset += 1; $cb -= 1; - switch(dechex($operand)) { + switch (dechex($operand)) { case 0x00: $operand = 'OFF'; break; @@ -1368,9 +1399,9 @@ private function readRecordPlcfBtePapx() } // - switch(dechex($sprm_Sgc)) { + switch (dechex($sprm_Sgc)) { case 0x01: // Sprm is modifying a paragraph property. - switch($sprm_IsPmd) { + switch ($sprm_IsPmd) { case 0x0A: // sprmPIlvl print_r('sprmPIlvl : '.$operand.PHP_EOL.PHP_EOL); break; @@ -1383,28 +1414,28 @@ private function readRecordPlcfBtePapx() } break; case 0x02: // Sprm is modifying a character property. - switch($sprm_IsPmd) { + switch ($sprm_IsPmd) { default: print_r('$sprm_IsPmd(2) : '.$sprm_IsPmd.PHP_EOL.PHP_EOL); break; } break; case 0x03: // Sprm is modifying a picture property. - switch($sprm_IsPmd) { + switch ($sprm_IsPmd) { default: print_r('$sprm_IsPmd(3) : '.$sprm_IsPmd.PHP_EOL.PHP_EOL); break; } break; case 0x04: // Sprm is modifying a section property. - switch($sprm_IsPmd) { + switch ($sprm_IsPmd) { default: print_r('$sprm_IsPmd(4) : '.$sprm_IsPmd.PHP_EOL.PHP_EOL); break; } break; case 0x05: // Sprm is modifying a table property. - switch($sprm_IsPmd) { + switch ($sprm_IsPmd) { default: print_r('$sprm_IsPmd(4) : '.$sprm_IsPmd.PHP_EOL.PHP_EOL); break; @@ -1418,7 +1449,7 @@ private function readRecordPlcfBtePapx() } else { if ($istd > 0) { // @todo : Determining Properties of a Paragraph Style - # @link http://msdn.microsoft.com/en-us/library/dd948631%28v=office.12%29.aspx + # @see http://msdn.microsoft.com/en-us/library/dd948631%28v=office.12%29.aspx } } }*/ @@ -1426,15 +1457,16 @@ private function readRecordPlcfBtePapx() } /** - * Character formatting properties to text in a document - * @link http://msdn.microsoft.com/en-us/library/dd907108%28v=office.12%29.aspx + * Character formatting properties to text in a document. + * + * @see http://msdn.microsoft.com/en-us/library/dd907108%28v=office.12%29.aspx */ - private function readRecordPlcfBteChpx() + private function readRecordPlcfBteChpx(): void { $posMem = $this->arrayFib['fcPlcfBteChpx']; $num = $this->getNumInLcb($this->arrayFib['lcbPlcfBteChpx'], 4); - $aPnBteChpx = array(); - for ($inc = 0; $inc <= $num; $inc++) { + $aPnBteChpx = []; + for ($inc = 0; $inc <= $num; ++$inc) { $aPnBteChpx[$inc] = self::getInt4d($this->data1Table, $posMem); $posMem += 4; } @@ -1445,34 +1477,34 @@ private function readRecordPlcfBteChpx() $offset = $offsetBase; // ChpxFkp - // @link : 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 = array(); - for ($inc = 0; $inc <= $numRGFC; $inc++) { + $arrayRGFC = []; + for ($inc = 0; $inc <= $numRGFC; ++$inc) { $arrayRGFC[$inc] = self::getInt4d($this->dataWorkDocument, $offset); $offset += 4; } - $arrayRGB = array(); - for ($inc = 1; $inc <= $numRGFC; $inc++) { + $arrayRGB = []; + for ($inc = 1; $inc <= $numRGFC; ++$inc) { $arrayRGB[$inc] = self::getInt1d($this->dataWorkDocument, $offset); - $offset += 1; + ++$offset; } $start = 0; foreach ($arrayRGB as $keyRGB => $rgb) { - $oStyle = new \stdClass(); + $oStyle = new stdClass(); $oStyle->pos_start = $start; - $oStyle->pos_len = (int)ceil((($arrayRGFC[$keyRGB] -1) - $arrayRGFC[$keyRGB -1]) / 2); + $oStyle->pos_len = (int) ceil((($arrayRGFC[$keyRGB] - 1) - $arrayRGFC[$keyRGB - 1]) / 2); $start += $oStyle->pos_len; if ($rgb > 0) { // Chp Structure - // @link : 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); - $posRGB += 1; + ++$posRGB; $oStyle->style = $this->readPrl($this->dataWorkDocument, $posRGB, $cb); $posRGB += $oStyle->style->length; @@ -1482,23 +1514,24 @@ private function readRecordPlcfBteChpx() } /** - * @param $sprm - * @return \stdClass + * @return stdClass */ private function readSprm($sprm) { - $oSprm = new \stdClass(); + $oSprm = new stdClass(); $oSprm->isPmd = $sprm & 0x01FF; - $oSprm->f = ($sprm / 512) & 0x0001; - $oSprm->sgc = ($sprm / 1024) & 0x0007; - $oSprm->spra = ($sprm / 8192); + $oSprm->f = (int) ($sprm / 512) & 0x0001; + $oSprm->sgc = (int) ($sprm / 1024) & 0x0007; + $oSprm->spra = (int) ($sprm / 8192); + return $oSprm; } /** * @param string $data - * @param integer $pos - * @param \stdClass $oSprm + * @param int $pos + * @param stdClass $oSprm + * * @return array */ private function readSprmSpra($data, $pos, $oSprm) @@ -1506,70 +1539,82 @@ private function readSprmSpra($data, $pos, $oSprm) $length = 0; $operand = null; - switch(dechex($oSprm->spra)) { + switch (dechex($oSprm->spra)) { case 0x0: $operand = self::getInt1d($data, $pos); $length = 1; - switch(dechex($operand)) { + switch (dechex($operand)) { case 0x00: $operand = false; + break; case 0x01: $operand = true; + break; case 0x80: $operand = self::SPRA_VALUE; + break; case 0x81: $operand = self::SPRA_VALUE_OPPOSITE; + break; } + break; case 0x1: $operand = self::getInt1d($data, $pos); $length = 1; + break; case 0x2: case 0x4: case 0x5: $operand = self::getInt2d($data, $pos); $length = 2; + break; case 0x3: if ($oSprm->isPmd != 0x70) { $operand = self::getInt4d($data, $pos); $length = 4; } + break; case 0x7: $operand = self::getInt3d($data, $pos); $length = 3; + break; default: // print_r('YO YO YO : '.PHP_EOL); } - return array( + return [ 'length' => $length, 'operand' => $operand, - ); + ]; } /** - * @param $data integer - * @param $pos integer - * @return \stdClass - * @link http://msdn.microsoft.com/en-us/library/dd772849%28v=office.12%29.aspx + * @param $data int + * @param $pos int + * @param $cbNum int + * + * @return stdClass + * + * @see http://msdn.microsoft.com/en-us/library/dd772849%28v=office.12%29.aspx */ private function readPrl($data, $pos, $cbNum) { $posStart = $pos; - $oStylePrl = new \stdClass(); + $oStylePrl = new stdClass(); // Variables $sprmCPicLocation = null; $sprmCFData = null; - $sprmCFSpec = null; + //$sprmCFSpec = null; do { // Variables @@ -1585,203 +1630,254 @@ private function readPrl($data, $pos, $cbNum) $cbNum -= $arrayReturn['length']; $operand = $arrayReturn['operand']; - switch(dechex($oSprm->sgc)) { + switch (dechex($oSprm->sgc)) { // Paragraph property case 0x01: break; - // Character property + // Character property case 0x02: if (!isset($oStylePrl->styleFont)) { - $oStylePrl->styleFont = array(); + $oStylePrl->styleFont = []; } - switch($oSprm->isPmd) { + switch ($oSprm->isPmd) { // sprmCFRMarkIns case 0x01: break; - // sprmCFFldVanish + // sprmCFFldVanish case 0x02: break; - // sprmCPicLocation + // sprmCPicLocation case 0x03: $sprmCPicLocation = $operand; + break; - // sprmCFData + // sprmCFData case 0x06: - $sprmCFData = dechex($operand) == 0x00 ? false : true; + $sprmCFData = dechex($operand) != 0x00; + break; - // sprmCFItalic + // sprmCFItalic case 0x36: // By default, text is not italicized. - switch($operand) { + switch ($operand) { case false: case true: $oStylePrl->styleFont['italic'] = $operand; + break; case self::SPRA_VALUE: $oStylePrl->styleFont['italic'] = false; + break; case self::SPRA_VALUE_OPPOSITE: $oStylePrl->styleFont['italic'] = true; + break; } + break; - // sprmCIstd + // sprmCIstd case 0x30: //print_r('sprmCIstd : '.dechex($operand).PHP_EOL.PHP_EOL); break; - // sprmCFBold + // sprmCFBold case 0x35: // By default, text is not bold. - switch($operand) { + switch ($operand) { case false: case true: $oStylePrl->styleFont['bold'] = $operand; + break; case self::SPRA_VALUE: $oStylePrl->styleFont['bold'] = false; + break; case self::SPRA_VALUE_OPPOSITE: $oStylePrl->styleFont['bold'] = true; + break; } + break; - // sprmCFStrike + // sprmCFStrike case 0x37: // By default, text is not struck through. - switch($operand) { + switch ($operand) { case false: case true: $oStylePrl->styleFont['strikethrough'] = $operand; + break; case self::SPRA_VALUE: $oStylePrl->styleFont['strikethrough'] = false; + break; case self::SPRA_VALUE_OPPOSITE: $oStylePrl->styleFont['strikethrough'] = true; + break; } + break; - // sprmCKul + // sprmCKul case 0x3E: - switch(dechex($operand)) { + switch (dechex($operand)) { case 0x00: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_NONE; + break; case 0x01: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_SINGLE; + break; case 0x02: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WORDS; + break; case 0x03: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOUBLE; + break; case 0x04: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTTED; + break; case 0x06: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_HEAVY; + break; case 0x07: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASH; + break; case 0x09: - $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTHASH; + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDASH; + break; case 0x0A: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDOTDASH; + break; case 0x0B: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WAVY; + break; case 0x14: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTTEDHEAVY; + break; case 0x17: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASHHEAVY; + break; case 0x19: - $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTHASHHEAVY; + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDASHHEAVY; + break; case 0x1A: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDOTDASHHEAVY; + break; case 0x1B: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WAVYHEAVY; + break; case 0x27: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASHLONG; + break; case 0x2B: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WAVYDOUBLE; + break; case 0x37: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASHLONGHEAVY; + break; default: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_NONE; + break; } + break; - // sprmCIco - //@link http://msdn.microsoft.com/en-us/library/dd773060%28v=office.12%29.aspx + // sprmCIco + //@see http://msdn.microsoft.com/en-us/library/dd773060%28v=office.12%29.aspx case 0x42: - switch(dechex($operand)) { + switch (dechex($operand)) { case 0x00: case 0x01: $oStylePrl->styleFont['color'] = '000000'; + break; case 0x02: $oStylePrl->styleFont['color'] = '0000FF'; + break; case 0x03: $oStylePrl->styleFont['color'] = '00FFFF'; + break; case 0x04: $oStylePrl->styleFont['color'] = '00FF00'; + break; case 0x05: $oStylePrl->styleFont['color'] = 'FF00FF'; + break; case 0x06: $oStylePrl->styleFont['color'] = 'FF0000'; + break; case 0x07: $oStylePrl->styleFont['color'] = 'FFFF00'; + break; case 0x08: $oStylePrl->styleFont['color'] = 'FFFFFF'; + break; case 0x09: $oStylePrl->styleFont['color'] = '000080'; + break; case 0x0A: $oStylePrl->styleFont['color'] = '008080'; + break; case 0x0B: $oStylePrl->styleFont['color'] = '008000'; + break; case 0x0C: $oStylePrl->styleFont['color'] = '800080'; + break; case 0x0D: $oStylePrl->styleFont['color'] = '800080'; + break; case 0x0E: $oStylePrl->styleFont['color'] = '808000'; + break; case 0x0F: $oStylePrl->styleFont['color'] = '808080'; + break; case 0x10: $oStylePrl->styleFont['color'] = 'C0C0C0'; } + break; - // sprmCHps + // sprmCHps case 0x43: - $oStylePrl->styleFont['size'] = dechex($operand/2); + $oStylePrl->styleFont['size'] = $operand / 2; + break; - // sprmCIss + // sprmCIss case 0x48: if (!isset($oStylePrl->styleFont['superScript'])) { $oStylePrl->styleFont['superScript'] = false; @@ -1795,139 +1891,152 @@ private function readPrl($data, $pos, $cbNum) break; case 0x01: $oStylePrl->styleFont['superScript'] = true; + break; case 0x02: $oStylePrl->styleFont['subScript'] = true; + break; } + break; - // sprmCRgFtc0 + // sprmCRgFtc0 case 0x4F: $oStylePrl->styleFont['name'] = ''; if (isset($this->arrayFonts[$operand])) { $oStylePrl->styleFont['name'] = $this->arrayFonts[$operand]['main']; } + break; - // sprmCRgFtc1 + // sprmCRgFtc1 case 0x50: // if the language for the text is an East Asian language break; - // sprmCRgFtc2 + // sprmCRgFtc2 case 0x51: // if the character falls outside the Unicode character range break; - // sprmCFSpec + // sprmCFSpec case 0x55: - $sprmCFSpec = $operand; + //$sprmCFSpec = $operand; break; - // sprmCFtcBi + // sprmCFtcBi case 0x5E: break; - // sprmCFItalicBi + // sprmCFItalicBi case 0x5D: break; - // sprmCHpsBi + // sprmCHpsBi case 0x61: break; - // sprmCShd80 - //@link http://msdn.microsoft.com/en-us/library/dd923447%28v=office.12%29.aspx + // sprmCShd80 + //@see http://msdn.microsoft.com/en-us/library/dd923447%28v=office.12%29.aspx case 0x66: // $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 - //@link : http://msdn.microsoft.com/en-us/library/dd952824%28v=office.12%29.aspx + // sprmCCv + //@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 += 1; + ++$pos; $green = str_pad(dechex(self::getInt1d($this->dataWorkDocument, $pos)), 2, '0', STR_PAD_LEFT); - $pos += 1; + ++$pos; $blue = str_pad(dechex(self::getInt1d($this->dataWorkDocument, $pos)), 2, '0', STR_PAD_LEFT); - $pos += 1; - $pos += 1; - $oStylePrl->styleFont['color'] = $red.$green.$blue; + ++$pos; + ++$pos; + $oStylePrl->styleFont['color'] = $red . $green . $blue; $cbNum -= 4; + break; default: // print_r('@todo Character : 0x'.dechex($oSprm->isPmd)); // print_r(PHP_EOL); } + break; - // Picture property + // Picture property case 0x03: break; - // Section property + // Section property case 0x04: if (!isset($oStylePrl->styleSection)) { - $oStylePrl->styleSection = array(); + $oStylePrl->styleSection = []; } - switch($oSprm->isPmd) { + switch ($oSprm->isPmd) { // sprmSNfcPgn case 0x0E: // numbering format used for page numbers break; - // sprmSXaPage + // sprmSXaPage case 0x1F: $oStylePrl->styleSection['pageSizeW'] = $operand; + break; - // sprmSYaPage + // sprmSYaPage case 0x20: $oStylePrl->styleSection['pageSizeH'] = $operand; + break; - // sprmSDxaLeft + // sprmSDxaLeft case 0x21: $oStylePrl->styleSection['marginLeft'] = $operand; + break; - // sprmSDxaRight + // sprmSDxaRight case 0x22: $oStylePrl->styleSection['marginRight'] = $operand; + break; - // sprmSDyaTop + // sprmSDyaTop case 0x23: $oStylePrl->styleSection['marginTop'] = $operand; + break; - // sprmSDyaBottom + // sprmSDyaBottom case 0x24: $oStylePrl->styleSection['marginBottom'] = $operand; + break; - // sprmSFBiDi + // sprmSFBiDi case 0x28: // RTL layout break; - // sprmSDxtCharSpace + // sprmSDxtCharSpace case 0x30: // characpter pitch break; - // sprmSDyaLinePitch + // sprmSDyaLinePitch case 0x31: // line height break; - // sprmSClm + // sprmSClm case 0x32: // document grid mode break; - // sprmSTextFlow + // sprmSTextFlow case 0x33: // text flow break; default: // print_r('@todo Section : 0x'.dechex($oSprm->isPmd)); // print_r(PHP_EOL); - } + break; - // Table property + // Table property case 0x05: break; } } while ($cbNum > 0); - if (!is_null($sprmCPicLocation)) { - if (!is_null($sprmCFData) && $sprmCFData == 0x01) { + if (null !== $sprmCPicLocation) { + if (null !== $sprmCFData && $sprmCFData == 0x01) { // NilPICFAndBinData //@todo Read Hyperlink structure /*$lcb = self::getInt4d($this->dataData, $sprmCPicLocation); @@ -1943,7 +2052,7 @@ private function readPrl($data, $pos, $cbNum) // HFD > clsid $sprmCPicLocation += 16; // HFD > hyperlink - //@link : 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); @@ -2011,8 +2120,8 @@ private function readPrl($data, $pos, $cbNum) }*/ } else { // Pictures - //@link : http://msdn.microsoft.com/en-us/library/dd925458%28v=office.12%29.aspx - //@link : 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 @@ -2059,9 +2168,9 @@ private function readPrl($data, $pos, $cbNum) $picmidDxaCropBottom = self::getInt2d($this->dataData, $sprmCPicLocation); $sprmCPicLocation += 2; // PICF : picmid : fReserved - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // PICF : picmid : bpp - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // PICF : picmid : brcTop80 $sprmCPicLocation += 4; // PICF : picmid : brcLeft80 @@ -2080,14 +2189,14 @@ private function readPrl($data, $pos, $cbNum) if ($mfpfMm == 0x0066) { // cchPicName $cchPicName = self::getInt1d($this->dataData, $sprmCPicLocation); - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // stPicName - $stPicName = ''; - for ($inc = 0; $inc <= $cchPicName; $inc++) { - $chr = self::getInt1d($this->dataData, $sprmCPicLocation); - $sprmCPicLocation += 1; - $stPicName .= chr($chr); + //$stPicName = ''; + for ($inc = 0; $inc <= $cchPicName; ++$inc) { + //$chr = self::getInt1d($this->dataData, $sprmCPicLocation); + ++$sprmCPicLocation; + //$stPicName .= chr($chr); } } @@ -2099,18 +2208,18 @@ private function readPrl($data, $pos, $cbNum) $sprmCPicLocation += $shapeRH['recLen']; } // picture : rgfb - //@link : 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 - //@link : 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 += 1; + ++$sprmCPicLocation; // btMacOS - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // rgbUid $sprmCPicLocation += 16; // tag @@ -2122,31 +2231,31 @@ private function readPrl($data, $pos, $cbNum) // foDelay $sprmCPicLocation += 4; // unused1 - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // cbName $cbName = self::getInt1d($this->dataData, $sprmCPicLocation); - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // unused2 - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // unused3 - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // nameData if ($cbName > 0) { - $nameData = ''; - for ($inc = 0; $inc <= ($cbName / 2); $inc++) { - $chr = self::getInt2d($this->dataData, $sprmCPicLocation); + //$nameData = ''; + for ($inc = 0; $inc <= ($cbName / 2); ++$inc) { + //$chr = self::getInt2d($this->dataData, $sprmCPicLocation); $sprmCPicLocation += 2; - $nameData .= chr($chr); + //$nameData .= chr($chr); } } // embeddedBlip - //@link : 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: case self::OFFICEARTBLIPJPEG: if (!isset($oStylePrl->image)) { - $oStylePrl->image = array(); + $oStylePrl->image = []; } $sprmCPicLocation += 8; // embeddedBlip : rgbUid1 @@ -2156,7 +2265,7 @@ private function readPrl($data, $pos, $cbNum) $sprmCPicLocation += 16; } // embeddedBlip : tag - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // embeddedBlip : BLIPFileData $oStylePrl->image['data'] = substr($this->dataData, $sprmCPicLocation, $embeddedBlipRH['recLen']); $oStylePrl->image['format'] = 'jpg'; @@ -2173,10 +2282,14 @@ private function readPrl($data, $pos, $cbNum) $oStylePrl->image['height'] = Drawing::twipsToPixels($iCropHeight * $picmidMy / 1000); $sprmCPicLocation += $embeddedBlipRH['recLen']; + + break; + case self::OFFICEARTBLIPPNG: break; default: // print_r(dechex($embeddedBlipRH['recType'])); } + break; } $fileBlockRH = $this->loadRecordHeader($this->dataData, $sprmCPicLocation); @@ -2185,13 +2298,16 @@ private function readPrl($data, $pos, $cbNum) } $oStylePrl->length = $pos - $posStart; + return $oStylePrl; } /** - * Read a record header + * Read a record header. + * * @param string $stream - * @param integer $pos + * @param int $pos + * * @return array */ private function loadRecordHeader($stream, $pos) @@ -2199,25 +2315,26 @@ private function loadRecordHeader($stream, $pos) $rec = self::getInt2d($stream, $pos); $recType = self::getInt2d($stream, $pos + 2); $recLen = self::getInt4d($stream, $pos + 4); - return array( + + return [ 'recVer' => ($rec >> 0) & bindec('1111'), 'recInstance' => ($rec >> 4) & bindec('111111111111'), 'recType' => $recType, 'recLen' => $recLen, - ); + ]; } - private function generatePhpWord() + private function generatePhpWord(): void { foreach ($this->arraySections as $itmSection) { $oSection = $this->phpWord->addSection(); - $oSection->setSettings($itmSection->styleSection); + $oSection->setStyle($itmSection->styleSection); $sHYPERLINK = ''; 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) == '') { @@ -2228,7 +2345,7 @@ private function generatePhpWord() } // Style Character - $styleFont = array(); + $styleFont = []; if (isset($oCharacters->style)) { if (isset($oCharacters->style->styleFont)) { $styleFont = $oCharacters->style->styleFont; @@ -2266,9 +2383,9 @@ private function generatePhpWord() } if (ord($sText[0]) == 1) { if (isset($oCharacters->style->image)) { - $fileImage = tempnam(sys_get_temp_dir(), 'PHPWord_MsDoc').'.'.$oCharacters->style->image['format']; + $fileImage = tempnam(sys_get_temp_dir(), 'PHPWord_MsDoc') . '.' . $oCharacters->style->image['format']; file_put_contents($fileImage, $oCharacters->style->image['data']); - $oSection->addImage($fileImage, array('width' => $oCharacters->style->image['width'], 'height' => $oCharacters->style->image['height'])); + $oSection->addImage($fileImage, ['width' => $oCharacters->style->image['width'], 'height' => $oCharacters->style->image['height']]); // print_r('>addImage<'.$fileImage.'>'.EOL); } } @@ -2277,15 +2394,15 @@ private function generatePhpWord() } } } - } } /** - * Read 8-bit unsigned integer + * Read 8-bit unsigned integer. * * @param string $data * @param int $pos + * * @return int */ public static function getInt1d($data, $pos) @@ -2294,34 +2411,37 @@ public static function getInt1d($data, $pos) } /** - * Read 16-bit unsigned integer + * Read 16-bit unsigned integer. * * @param string $data * @param int $pos + * * @return int */ public static function getInt2d($data, $pos) { - return ord($data[$pos]) | (ord($data[$pos+1]) << 8); + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8); } /** - * Read 24-bit signed integer + * Read 24-bit signed integer. * * @param string $data * @param int $pos + * * @return int */ public static function getInt3d($data, $pos) { - return ord($data[$pos]) | (ord($data[$pos+1]) << 8) | (ord($data[$pos+2]) << 16); + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16); } /** - * Read 32-bit signed integer + * Read 32-bit signed integer. * * @param string $data * @param int $pos + * * @return int */ public static function getInt4d($data, $pos) @@ -2336,6 +2456,7 @@ public static function getInt4d($data, $pos) } else { $ord24 = ($or24 & 127) << 24; } - return ord($data[$pos]) | (ord($data[$pos+1]) << 8) | (ord($data[$pos+2]) << 16) | $ord24; + + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24; } } diff --git a/src/PhpWord/Reader/ODText.php b/src/PhpWord/Reader/ODText.php index d992f7fd3c..288bf9a0dd 100644 --- a/src/PhpWord/Reader/ODText.php +++ b/src/PhpWord/Reader/ODText.php @@ -1,4 +1,5 @@ readRelationships($docFile); - $readerParts = array( + $readerParts = [ 'content.xml' => 'Content', 'meta.xml' => 'Meta', - ); + ]; foreach ($readerParts as $xmlFile => $partName) { $this->readPart($phpWord, $relationships, $partName, $docFile, $xmlFile); @@ -52,19 +54,12 @@ public function load($docFile) /** * Read document part. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord - * @param array $relationships - * @param string $partName - * @param string $docFile - * @param string $xmlFile - * @return void */ - private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile) + private function readPart(PhpWord $phpWord, array $relationships, string $partName, string $docFile, string $xmlFile): void { $partClass = "PhpOffice\\PhpWord\\Reader\\ODText\\{$partName}"; if (class_exists($partClass)) { - /** @var \PhpOffice\PhpWord\Reader\ODText\AbstractPart $part Type hint */ + /** @var ODText\AbstractPart $part Type hint */ $part = new $partClass($docFile, $xmlFile); $part->setRels($relationships); $part->read($phpWord); @@ -72,14 +67,11 @@ private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, } /** - * Read all relationship files - * - * @param string $docFile - * @return array + * Read all relationship files. */ - private function readRelationships($docFile) + private function readRelationships(string $docFile): array { - $rels = array(); + $rels = []; $xmlFile = 'META-INF/manifest.xml'; $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($docFile, $xmlFile); @@ -87,7 +79,7 @@ private function readRelationships($docFile) foreach ($nodes as $node) { $type = $xmlReader->getAttribute('manifest:media-type', $node); $target = $xmlReader->getAttribute('manifest:full-path', $node); - $rels[] = array('type' => $type, 'target' => $target); + $rels[] = ['type' => $type, 'target' => $target]; } return $rels; diff --git a/src/PhpWord/Reader/ODText/AbstractPart.php b/src/PhpWord/Reader/ODText/AbstractPart.php index 95f700847e..447c96f094 100644 --- a/src/PhpWord/Reader/ODText/AbstractPart.php +++ b/src/PhpWord/Reader/ODText/AbstractPart.php @@ -1,4 +1,5 @@ getDomFromZip($this->docFile, $this->xmlFile); $nodes = $xmlReader->getElements('office:body/office:text/*'); + $this->section = null; + $this->processNodes($nodes, $xmlReader, $phpWord); + $this->section = null; + } + + /** @param DOMNodeList $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); - break; + $this->getSection($phpWord)->addTitle($node->nodeValue, $depth); - case 'text:p': // Paragraph - $section->addText($node->nodeValue); break; + case 'text:p': // Paragraph + $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'; + + $xmlReaderObject = new XMLReader(); + $mathElement = $xmlReaderObject->getDomFromZip($this->docFile, $mathFile); + if ($mathElement) { + $mathXML = $mathElement->saveXML($mathElement); + + if (is_string($mathXML)) { + $reader = new MathML(); + $math = $reader->read($mathXML); + + $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; + } + } + 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']); + } + } + } + } + + break; case 'text:list': // List $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; + case 'text:tracked-changes': + $changedRegions = $xmlReader->getElements('text:changed-region', $node); + foreach ($changedRegions as $changedRegion) { + $type = ($changedRegion->firstChild->nodeName == 'text:insertion') ? TrackChange::INSERTED : TrackChange::DELETED; + $creatorNode = $xmlReader->getElements('office:change-info/dc:creator', $changedRegion->firstChild); + $author = $creatorNode[0]->nodeValue; + $dateNode = $xmlReader->getElements('office:change-info/dc:date', $changedRegion->firstChild); + $date = $dateNode[0]->nodeValue; + $date = preg_replace('/\.\d+$/', '', $date); + $date = DateTime::createFromFormat('Y-m-d\TH:i:s', $date); + $changed = new TrackChange($type, $author, $date); + $textNodes = $xmlReader->getElements('text:deletion/text:p', $changedRegion); + $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(); + /** @var DOMNodeList $children */ + $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/ODText/Meta.php b/src/PhpWord/Reader/ODText/Meta.php index d08ce3a679..1321b6ce35 100644 --- a/src/PhpWord/Reader/ODText/Meta.php +++ b/src/PhpWord/Reader/ODText/Meta.php @@ -1,4 +1,5 @@ getDomFromZip($this->docFile, $this->xmlFile); @@ -43,16 +42,16 @@ public function read(PhpWord $phpWord) $metaNode = $xmlReader->getElement('office:meta'); // Standard properties - $properties = array( - 'title' => 'dc:title', - 'subject' => 'dc:subject', - 'description' => 'dc:description', - 'keywords' => 'meta:keyword', - 'creator' => 'meta:initial-creator', + $properties = [ + 'title' => 'dc:title', + 'subject' => 'dc:subject', + 'description' => 'dc:description', + 'keywords' => 'meta:keyword', + 'creator' => 'meta:initial-creator', 'lastModifiedBy' => 'dc:creator', // 'created' => 'meta:creation-date', // 'modified' => 'dc:date', - ); + ]; foreach ($properties as $property => $path) { $method = "set{$property}"; $propertyNode = $xmlReader->getElement($path, $metaNode); @@ -67,12 +66,11 @@ public function read(PhpWord $phpWord) $property = $xmlReader->getAttribute('meta:name', $propertyNode); // Set category, company, and manager property - if (in_array($property, array('Category', 'Company', 'Manager'))) { + if (in_array($property, ['Category', 'Company', 'Manager'])) { $method = "set{$property}"; $docProps->$method($propertyNode->nodeValue); - - // Set other custom properties } else { + // Set other custom properties $docProps->setCustomProperty($property, $propertyNode->nodeValue); } } diff --git a/src/PhpWord/Reader/RTF.php b/src/PhpWord/Reader/RTF.php index 9d5d813bf4..dbf1ebd7ad 100644 --- a/src/PhpWord/Reader/RTF.php +++ b/src/PhpWord/Reader/RTF.php @@ -1,4 +1,5 @@ rtf = file_get_contents($docFile); $doc->read($phpWord); } else { - throw new \Exception("Cannot read {$docFile}."); + throw new Exception("Cannot read {$docFile}."); } return $phpWord; diff --git a/src/PhpWord/Reader/RTF/Document.php b/src/PhpWord/Reader/RTF/Document.php index 3f63e3398e..59f0a7dd59 100644 --- a/src/PhpWord/Reader/RTF/Document.php +++ b/src/PhpWord/Reader/RTF/Document.php @@ -1,4 +1,5 @@ 'markOpening', // { 125 => 'markClosing', // } - 92 => 'markBackslash', // \ - 10 => 'markNewline', // LF - 13 => 'markNewline' // CR - ); + 92 => 'markBackslash', // \ + 10 => 'markNewline', // LF + 13 => 'markNewline', // CR + ]; $this->phpWord = $phpWord; $this->section = $phpWord->addSection(); @@ -152,41 +153,39 @@ public function read(PhpWord $phpWord) // Walk each characters while ($this->offset < $this->length) { - $char = $this->rtf[$this->offset]; + $char = $this->rtf[$this->offset]; $ascii = ord($char); if (isset($markers[$ascii])) { // Marker found: {, }, \, LF, or CR $markerFunction = $markers[$ascii]; $this->$markerFunction(); } else { - if ($this->isControl === false) { // Non control word: Push character + if (false === $this->isControl) { // Non control word: Push character $this->pushText($char); } else { - if (preg_match("/^[a-zA-Z0-9-]?$/", $char)) { // No delimiter: Buffer control + if (preg_match('/^[a-zA-Z0-9-]?$/', $char)) { // No delimiter: Buffer control $this->control .= $char; $this->isFirst = false; } else { // Delimiter found: Parse buffered control if ($this->isFirst) { $this->isFirst = false; } else { - if ($char == ' ') { // Discard space as a control word delimiter + if (' ' == $char) { // Discard space as a control word delimiter $this->flushControl(true); } } } } } - $this->offset++; + ++$this->offset; } $this->flushText(); } /** * Mark opening braket `{` character. - * - * @return void */ - private function markOpening() + private function markOpening(): void { $this->flush(true); array_push($this->groups, $this->flags); @@ -194,10 +193,8 @@ private function markOpening() /** * Mark closing braket `}` character. - * - * @return void */ - private function markClosing() + private function markClosing(): void { $this->flush(true); $this->flags = array_pop($this->groups); @@ -205,10 +202,8 @@ private function markClosing() /** * Mark backslash `\` character. - * - * @return void */ - private function markBackslash() + private function markBackslash(): void { if ($this->isFirst) { $this->setControl(false); @@ -222,10 +217,8 @@ private function markBackslash() /** * Mark newline character: Flush control word because it's not possible to span multiline. - * - * @return void */ - private function markNewline() + private function markNewline(): void { if ($this->isControl) { $this->flushControl(true); @@ -236,9 +229,8 @@ private function markNewline() * Flush control word or text. * * @param bool $isControl - * @return void */ - private function flush($isControl = false) + private function flush($isControl = false): void { if ($this->isControl) { $this->flushControl($isControl); @@ -251,32 +243,29 @@ private function flush($isControl = false) * Flush control word. * * @param bool $isControl - * @return void */ - private function flushControl($isControl = false) + private function flushControl($isControl = false): void { - if (preg_match("/^([A-Za-z]+)(-?[0-9]*) ?$/", $this->control, $match) === 1) { - list(, $control, $parameter) = $match; + if (1 === preg_match('/^([A-Za-z]+)(-?[0-9]*) ?$/', $this->control, $match)) { + [, $control, $parameter] = $match; $this->parseControl($control, $parameter); } - if ($isControl === true) { + if (true === $isControl) { $this->setControl(false); } } /** * Flush text in queue. - * - * @return void */ - private function flushText() + private function flushText(): void { if ($this->text != '') { if (isset($this->flags['property'])) { // Set property $this->flags['value'] = $this->text; } else { // Set text - if ($this->flags['paragraph'] === true) { + if (true === $this->flags['paragraph']) { $this->flags['paragraph'] = false; $this->flags['text'] = $this->text; } @@ -295,9 +284,8 @@ private function flushText() * Reset control word and first char state. * * @param bool $value - * @return void */ - private function setControl($value) + private function setControl($value): void { $this->isControl = $value; $this->isFirst = $value; @@ -307,14 +295,13 @@ private function setControl($value) * Push text into queue. * * @param string $char - * @return void */ - private function pushText($char) + private function pushText($char): void { - if ($char == '<') { - $this->text .= "<"; - } elseif ($char == '>') { - $this->text .= ">"; + if ('<' == $char) { + $this->text .= '<'; + } elseif ('>' == $char) { + $this->text .= '>'; } else { $this->text .= $char; } @@ -325,34 +312,33 @@ private function pushText($char) * * @param string $control * @param string $parameter - * @return void */ - private function parseControl($control, $parameter) + private function parseControl($control, $parameter): void { - $controls = array( - 'par' => array(self::PARA, 'paragraph', true), - 'b' => array(self::STYL, 'font', 'bold', true), - 'i' => array(self::STYL, 'font', 'italic', true), - 'u' => array(self::STYL, 'font', 'underline', true), - 'strike' => array(self::STYL, 'font', 'strikethrough',true), - 'fs' => array(self::STYL, 'font', 'size', $parameter), - 'qc' => array(self::STYL, 'paragraph', 'align', 'center'), - 'sa' => array(self::STYL, 'paragraph', 'spaceAfter', $parameter), - 'fonttbl' => array(self::SKIP, 'fonttbl', null), - 'colortbl' => array(self::SKIP, 'colortbl', null), - 'info' => array(self::SKIP, 'info', null), - 'generator' => array(self::SKIP, 'generator', null), - 'title' => array(self::SKIP, 'title', null), - 'subject' => array(self::SKIP, 'subject', null), - 'category' => array(self::SKIP, 'category', null), - 'keywords' => array(self::SKIP, 'keywords', null), - 'comment' => array(self::SKIP, 'comment', null), - 'shppict' => array(self::SKIP, 'pic', null), - 'fldinst' => array(self::SKIP, 'link', null), - ); + $controls = [ + 'par' => [self::PARA, 'paragraph', true], + 'b' => [self::STYL, 'font', 'bold', true], + 'i' => [self::STYL, 'font', 'italic', true], + 'u' => [self::STYL, 'font', 'underline', true], + 'strike' => [self::STYL, 'font', 'strikethrough', true], + 'fs' => [self::STYL, 'font', 'size', $parameter], + 'qc' => [self::STYL, 'paragraph', 'alignment', Jc::CENTER], + 'sa' => [self::STYL, 'paragraph', 'spaceAfter', $parameter], + 'fonttbl' => [self::SKIP, 'fonttbl', null], + 'colortbl' => [self::SKIP, 'colortbl', null], + 'info' => [self::SKIP, 'info', null], + 'generator' => [self::SKIP, 'generator', null], + 'title' => [self::SKIP, 'title', null], + 'subject' => [self::SKIP, 'subject', null], + 'category' => [self::SKIP, 'category', null], + 'keywords' => [self::SKIP, 'keywords', null], + 'comment' => [self::SKIP, 'comment', null], + 'shppict' => [self::SKIP, 'pic', null], + 'fldinst' => [self::SKIP, 'link', null], + ]; if (isset($controls[$control])) { - list($function) = $controls[$control]; + [$function] = $controls[$control]; if (method_exists($this, $function)) { $directives = $controls[$control]; array_shift($directives); // remove the function variable; we won't need it @@ -365,11 +351,10 @@ private function parseControl($control, $parameter) * Read paragraph. * * @param array $directives - * @return void */ - private function readParagraph($directives) + private function readParagraph($directives): void { - list($property, $value) = $directives; + [$property, $value] = $directives; $this->textrun = $this->section->addTextRun(); $this->flags[$property] = $value; } @@ -378,11 +363,10 @@ private function readParagraph($directives) * Read style. * * @param array $directives - * @return void */ - private function readStyle($directives) + private function readStyle($directives): void { - list($style, $property, $value) = $directives; + [$style, $property, $value] = $directives; $this->flags['styles'][$style][$property] = $value; } @@ -390,21 +374,18 @@ private function readStyle($directives) * Read skip. * * @param array $directives - * @return void */ - private function readSkip($directives) + private function readSkip($directives): void { - list($property) = $directives; + [$property] = $directives; $this->flags['property'] = $property; $this->flags['skipped'] = true; } /** * Read text. - * - * @return void */ - private function readText() + private function readText(): void { $text = $this->textrun->addText($this->text); if (isset($this->flags['styles']['font'])) { diff --git a/src/PhpWord/Reader/ReaderInterface.php b/src/PhpWord/Reader/ReaderInterface.php index 361c413796..1dced12aba 100644 --- a/src/PhpWord/Reader/ReaderInterface.php +++ b/src/PhpWord/Reader/ReaderInterface.php @@ -1,4 +1,5 @@ readRelationships($docFile); - - $steps = array( - array('stepPart' => 'document', 'stepItems' => array( - 'styles' => 'Styles', - 'numbering' => 'Numbering', - )), - array('stepPart' => 'main', 'stepItems' => array( - 'officeDocument' => 'Document', - 'core-properties' => 'DocPropsCore', - 'extended-properties' => 'DocPropsApp', - 'custom-properties' => 'DocPropsCustom', - )), - array('stepPart' => 'document', 'stepItems' => array( - 'endnotes' => 'Endnotes', - 'footnotes' => 'Footnotes', - )), - ); + $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', + 'comments' => 'Comments', + ], + ], + ]; foreach ($steps as $step) { $stepPart = $step['stepPart']; $stepItems = $step['stepItems']; + if (!isset($relationships[$stepPart])) { + continue; + } foreach ($relationships[$stepPart] as $relItem) { $relType = $relItem['type']; 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(); } } } @@ -77,34 +99,35 @@ public function load($docFile) /** * Read document part. * - * @param \PhpOffice\PhpWord\PhpWord $phpWord - * @param array $relationships - * @param string $partName - * @param string $docFile - * @param string $xmlFile - * @return void + * @param array> $commentRefs */ - private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile) + 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; } /** - * Read all relationship files + * Read all relationship files. * * @param string $docFile + * * @return array */ private function readRelationships($docFile) { - $relationships = array(); + $relationships = []; // _rels/.rels $relationships['main'] = $this->getRels($docFile, '_rels/.rels'); @@ -113,10 +136,17 @@ private function readRelationships($docFile) $wordRelsPath = 'word/_rels/'; $zip = new ZipArchive(); if ($zip->open($docFile) === true) { - for ($i = 0; $i < $zip->numFiles; $i++) { + for ($i = 0; $i < $zip->numFiles; ++$i) { $xmlFile = $zip->getNameIndex($i); + if (!is_string($xmlFile)) { + continue; + } if ((substr($xmlFile, 0, strlen($wordRelsPath))) == $wordRelsPath && (substr($xmlFile, -1)) != '/') { - $docPart = str_replace('.xml.rels', '', str_replace($wordRelsPath, '', $xmlFile)); + $docPart = str_replace( + '.xml.rels', + '', + str_replace($wordRelsPath, '', $xmlFile) + ); $relationships[$docPart] = $this->getRels($docFile, $xmlFile, 'word/'); } } @@ -127,11 +157,12 @@ private function readRelationships($docFile) } /** - * Get relationship array + * Get relationship array. * * @param string $docFile * @param string $xmlFile * @param string $targetPrefix + * * @return array */ private function getRels($docFile, $xmlFile, $targetPrefix = '') @@ -139,7 +170,7 @@ private function getRels($docFile, $xmlFile, $targetPrefix = '') $metaPrefix = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/'; $officePrefix = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/'; - $rels = array(); + $rels = []; $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($docFile, $xmlFile); @@ -148,6 +179,7 @@ private function getRels($docFile, $xmlFile, $targetPrefix = '') $rId = $xmlReader->getAttribute('Id', $node); $type = $xmlReader->getAttribute('Type', $node); $target = $xmlReader->getAttribute('Target', $node); + $mode = $xmlReader->getAttribute('TargetMode', $node); // Remove URL prefixes from $type to make it easier to read $type = str_replace($metaPrefix, '', $type); @@ -155,12 +187,12 @@ private function getRels($docFile, $xmlFile, $targetPrefix = '') $docPart = str_replace('.xml', '', $target); // Do not add prefix to link source - if (!in_array($type, array('hyperlink'))) { + if ($type != 'hyperlink' && $mode != 'External') { $target = $targetPrefix . $target; } // Push to return array - $rels[$rId] = array('type' => $type, 'target' => $target, 'docPart' => $docPart); + $rels[$rId] = ['type' => $type, 'target' => $target, 'docPart' => $docPart, 'targetMode' => $mode]; } ksort($rels); diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 021bdba11e..9d49573d69 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -1,4 +1,5 @@ > + */ + protected $commentRefs = []; + + /** + * Image Loading. + * + * @var bool + */ + protected $imageLoading = true; /** * Read part. @@ -67,7 +95,7 @@ abstract class AbstractPart abstract public function read(PhpWord $phpWord); /** - * Create new instance + * Create new instance. * * @param string $docFile * @param string $xmlFile @@ -82,177 +110,533 @@ public function __construct($docFile, $xmlFile) * Set relationships. * * @param array $value - * @return void */ - public function setRels($value) + 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> + */ + public function getCommentReferences(): array + { + return $this->commentRefs; + } + + /** + * Set comment references. + * + * @param array> $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 + */ + 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. * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $domNode - * @param mixed $parent + * @param AbstractContainer $parent * @param string $docPart - * @return void * * @todo Get font style for preserve text */ - protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $parent, $docPart = 'document') + protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart = 'document'): void { // Paragraph style - $paragraphStyle = null; - $headingMatches = array(); - if ($xmlReader->elementExists('w:pPr', $domNode)) { - $paragraphStyle = $this->readParagraphStyle($xmlReader, $domNode); - if (is_array($paragraphStyle) && isset($paragraphStyle['styleName'])) { - preg_match('/Heading(\d)/', $paragraphStyle['styleName'], $headingMatches); - } - } + $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 ($fldCharType == 'begin') { - $ignoreText = true; - } elseif ($fldCharType == 'end') { - $ignoreText = false; - } + if ($xmlReader->elementExists('w:lastRenderedPageBreak', $node)) { + $parent->addPageBreak(); } - if (!is_null($instrText)) { + $instrText = $xmlReader->getValue('w:instrText', $node); + if (null !== $instrText) { $textContent .= '{' . $instrText . '}'; } else { - if ($ignoreText === false) { + 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($textContent, $fontStyle, $paragraphStyle); + $parent->addPreserveText(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8'), $fontStyle, $paragraphStyle); + + 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 - } elseif ($xmlReader->elementExists('w:pPr/w:numPr', $domNode)) { - $textContent = ''; + 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('w:r', $domNode); + $nodes = $xmlReader->getElements('*', $domNode); + + $listItemRun = $parent->addListItemRun($levelId, "PHPWordList{$numId}", $paragraphStyle); + foreach ($nodes as $node) { - $textContent .= $xmlReader->getValue('w:t', $node); + $this->readRun($xmlReader, $node, $listItemRun, $docPart, $paragraphStyle); } - $parent->addListItem($textContent, $levelId, null, "PHPWordList{$numId}", $paragraphStyle); - // Heading - } elseif (!empty($headingMatches)) { - $textContent = ''; - $nodes = $xmlReader->getElements('w:r', $domNode); - foreach ($nodes as $node) { - $textContent .= $xmlReader->getValue('w:t', $node); + return; + } + + // Heading or Title + $headingDepth = $xmlReader->elementExists('w:pPr', $domNode) ? $this->getHeadingDepth($paragraphStyle) : null; + if ($headingDepth !== null) { + $textContent = null; + $nodes = $xmlReader->getElements('w:r|w:hyperlink', $domNode); + $hasRubyElement = $xmlReader->elementExists('w:r/w:ruby', $domNode); + if ($nodes->length === 1 && !$hasRubyElement) { + $textContent = htmlspecialchars($xmlReader->getValue('w:t', $nodes->item(0)), ENT_QUOTES, 'UTF-8'); + } else { + $textContent = new TextRun($paragraphStyle); + foreach ($nodes as $node) { + $this->readRun($xmlReader, $node, $textContent, $docPart, $paragraphStyle); + } } - $parent->addTitle($textContent, $headingMatches[1]); + $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 { - $runCount = $xmlReader->countElements('w:r', $domNode); - $linkCount = $xmlReader->countElements('w:hyperlink', $domNode); - $runLinkCount = $runCount + $linkCount; - if ($runLinkCount == 0) { - $parent->addTextBreak(null, $paragraphStyle); - } else { - $nodes = $xmlReader->getElements('*', $domNode); - foreach ($nodes as $node) { - $this->readRun( - $xmlReader, - $node, - ($runLinkCount > 1) ? $parent->addTextRun($paragraphStyle) : $parent, - $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. + * + * @return null|number + */ + private function getHeadingDepth(?array $paragraphStyle = null) + { + if (is_array($paragraphStyle) && isset($paragraphStyle['styleName'])) { + if ('Title' === $paragraphStyle['styleName']) { + return 0; + } + + $headingMatches = []; + preg_match('/Heading(\d)/', $paragraphStyle['styleName'], $headingMatches); + if (!empty($headingMatches)) { + return $headingMatches[1]; + } } + + return null; } /** * Read w:r. * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $domNode - * @param mixed $parent + * @param AbstractContainer $parent * @param string $docPart * @param mixed $paragraphStyle - * @return void * * @todo Footnote paragraph style */ - protected function readRun(XMLReader $xmlReader, \DOMElement $domNode, $parent, $docPart, $paragraphStyle = null) + protected function readRun(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart, $paragraphStyle = null): void { - if (!in_array($domNode->nodeName, array('w:r', 'w:hyperlink'))) { - return; + 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); + } + } elseif ($domNode->nodeName == 'w:r') { + $fontStyle = $this->readFontStyle($xmlReader, $domNode); + $nodes = $xmlReader->getElements('*', $domNode); + foreach ($nodes as $node) { + $this->readRunChild($xmlReader, $node, $parent, $docPart, $paragraphStyle, $fontStyle); + } } - $fontStyle = $this->readFontStyle($xmlReader, $domNode); - // Link - if ($domNode->nodeName == 'w:hyperlink') { - $rId = $xmlReader->getAttribute('r:id', $domNode); - $textContent = $xmlReader->getValue('w:r/w:t', $domNode); - $target = $this->getMediaTarget($docPart, $rId); - if (!is_null($target)) { - $parent->addLink($target, $textContent, $fontStyle, $paragraphStyle); + 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)); } - } else { - // Footnote - if ($xmlReader->elementExists('w:footnoteReference', $domNode)) { - $parent->addFootnote(); + } + } + /** + * Parses nodes under w:r. + * + * @param string $docPart + * @param mixed $paragraphStyle + * @param mixed $fontStyle + */ + protected function readRunChild(XMLReader $xmlReader, DOMElement $node, AbstractContainer $parent, $docPart, $paragraphStyle = null, $fontStyle = null): void + { + $runParent = $node->parentNode->parentNode; + if ($node->nodeName == 'w:footnoteReference') { + // Footnote + $wId = $xmlReader->getAttribute('w:id', $node); + $footnote = $parent->addFootnote(); + $footnote->setRelationId($wId); + } elseif ($node->nodeName == 'w:endnoteReference') { // Endnote - } elseif ($xmlReader->elementExists('w:endnoteReference', $domNode)) { - $parent->addEndnote(); - + $wId = $xmlReader->getAttribute('w:id', $node); + $endnote = $parent->addEndnote(); + $endnote->setRelationId($wId); + } elseif ($node->nodeName == 'w:pict') { // Image - } elseif ($xmlReader->elementExists('w:pict', $domNode)) { - $rId = $xmlReader->getAttribute('r:id', $domNode, 'w:pict/v:shape/v:imagedata'); - $target = $this->getMediaTarget($docPart, $rId); - if (!is_null($target)) { + $rId = $xmlReader->getAttribute('r:id', $node, 'v:shape/v:imagedata'); + $target = $this->getMediaTarget($docPart, $rId); + if ($this->hasImageLoading() && null !== $target) { + if ('External' == $this->getTargetMode($docPart, $rId)) { + $imageSource = $target; + } else { $imageSource = "zip://{$this->docFile}#{$target}"; - $parent->addImage($imageSource); } + $parent->addImage($imageSource); + } + } elseif ($node->nodeName == 'w:drawing') { + // Office 2011 Image + $xmlReader->registerNamespace('wp', 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'); + $xmlReader->registerNamespace('r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlReader->registerNamespace('pic', 'http://schemas.openxmlformats.org/drawingml/2006/picture'); + $xmlReader->registerNamespace('a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $name = $xmlReader->getAttribute('name', $node, 'wp:inline/a:graphic/a:graphicData/pic:pic/pic:nvPicPr/pic:cNvPr'); + $embedId = $xmlReader->getAttribute('r:embed', $node, 'wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip'); + if ($name === null && $embedId === null) { // some Converters puts images on a different path + $name = $xmlReader->getAttribute('name', $node, 'wp:anchor/a:graphic/a:graphicData/pic:pic/pic:nvPicPr/pic:cNvPr'); + $embedId = $xmlReader->getAttribute('r:embed', $node, 'wp:anchor/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip'); + } + $target = $this->getMediaTarget($docPart, $embedId); + if ($this->hasImageLoading() && null !== $target) { + $imageSource = "zip://{$this->docFile}#{$target}"; + $parent->addImage($imageSource, null, false, $name); + } + } elseif ($node->nodeName == 'w:object') { // Object - } elseif ($xmlReader->elementExists('w:object', $domNode)) { - $rId = $xmlReader->getAttribute('r:id', $domNode, 'w:object/o:OLEObject'); - // $rIdIcon = $xmlReader->getAttribute('r:id', $domNode, 'w:object/v:shape/v:imagedata'); - $target = $this->getMediaTarget($docPart, $rId); - if (!is_null($target)) { - $textContent = ""; + $rId = $xmlReader->getAttribute('r:id', $node, 'o:OLEObject'); + // $rIdIcon = $xmlReader->getAttribute('r:id', $domNode, 'w:object/v:shape/v:imagedata'); + $target = $this->getMediaTarget($docPart, $rId); + if (null !== $target) { + $textContent = "<Object: {$target}>"; + $parent->addText($textContent, $fontStyle, $paragraphStyle); + } + } elseif ($node->nodeName == 'w:br') { + $parent->addTextBreak(); + } elseif ($node->nodeName == 'w:tab') { + $parent->addText("\t"); + } elseif ($node->nodeName == 'mc:AlternateContent') { + if ($node->hasChildNodes()) { + // Get fallback instead of mc:Choice to make sure it is compatible + $fallbackElements = $node->getElementsByTagName('Fallback'); + + if ($fallbackElements->length) { + $fallback = $fallbackElements->item(0); + // TextRun + $textContent = htmlspecialchars($fallback->nodeValue, ENT_QUOTES, 'UTF-8'); + $parent->addText($textContent, $fontStyle, $paragraphStyle); } - + } + } elseif ($node->nodeName == 'w:t' || $node->nodeName == 'w:delText') { // TextRun + $textContent = htmlspecialchars($xmlReader->getValue('.', $node), ENT_QUOTES, 'UTF-8'); + + if ($runParent->nodeName == 'w:hyperlink') { + $rId = $xmlReader->getAttribute('r:id', $runParent); + $target = $this->getMediaTarget($docPart, $rId); + if (null !== $target) { + $parent->addLink($target, $textContent, $fontStyle, $paragraphStyle); + } else { + $parent->addText($textContent, $fontStyle, $paragraphStyle); + } } else { - $textContent = $xmlReader->getValue('w:t', $domNode); - $parent->addText($textContent, $fontStyle, $paragraphStyle); + /** @var AbstractElement $element */ + $element = $parent->addText($textContent, $fontStyle, $paragraphStyle); + if (in_array($runParent->nodeName, ['w:ins', 'w:del'])) { + $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); + } elseif ($node->nodeName == 'w:ruby') { + $rubyPropertiesNode = $xmlReader->getElement('w:rubyPr', $node); + $properties = $this->readRubyProperties($xmlReader, $rubyPropertiesNode); + // read base text node + $baseText = new TextRun($paragraphStyle); + $baseTextNode = $xmlReader->getElement('w:rubyBase/w:r', $node); + $this->readRun($xmlReader, $baseTextNode, $baseText, $docPart, $paragraphStyle); + // read the actual ruby text (e.g. furigana in Japanese) + $rubyText = new TextRun($paragraphStyle); + $rubyTextNode = $xmlReader->getElement('w:rt/w:r', $node); + $this->readRun($xmlReader, $rubyTextNode, $rubyText, $docPart, $paragraphStyle); + // add element to parent + $parent->addRuby($baseText, $rubyText, $properties); } } + /** + * Read w:rubyPr element. + * + * @param XMLReader $xmlReader reader for XML + * @param DOMElement $domNode w:RubyPr element + * + * @return RubyProperties ruby properties from element + */ + protected function readRubyProperties(XMLReader $xmlReader, DOMElement $domNode): RubyProperties + { + $rubyAlignment = $xmlReader->getElement('w:rubyAlign', $domNode)->getAttribute('w:val'); + $rubyHps = $xmlReader->getElement('w:hps', $domNode)->getAttribute('w:val'); // font face + $rubyHpsRaise = $xmlReader->getElement('w:hpsRaise', $domNode)->getAttribute('w:val'); // pts above base text + $rubyHpsBaseText = $xmlReader->getElement('w:hpsBaseText', $domNode)->getAttribute('w:val'); // base text size + $rubyLid = $xmlReader->getElement('w:lid', $domNode)->getAttribute('w:val'); // type of ruby + $properties = new RubyProperties(); + $properties->setAlignment($rubyAlignment); + $properties->setFontFaceSize((float) $rubyHps); + $properties->setFontPointsAboveBaseText((float) $rubyHpsRaise); + $properties->setFontSizeForBaseText((float) $rubyHpsBaseText); + $properties->setLanguageId($rubyLid); + + return $properties; + } + /** * Read w:tbl. * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $domNode * @param mixed $parent * @param string $docPart - * @return void */ - protected function readTable(XMLReader $xmlReader, \DOMElement $domNode, $parent, $docPart = 'document') + protected function readTable(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart = 'document'): void { // Table style $tblStyle = null; @@ -264,38 +648,37 @@ protected function readTable(XMLReader $xmlReader, \DOMElement $domNode, $parent $table = $parent->addTable($tblStyle); $tblNodes = $xmlReader->getElements('*', $domNode); foreach ($tblNodes as $tblNode) { - if ($tblNode->nodeName == 'w:tblGrid') { // Column + if ('w:tblGrid' == $tblNode->nodeName) { // Column // @todo Do something with table columns - - } elseif ($tblNode->nodeName == 'w:tr') { // Row + } elseif ('w:tr' == $tblNode->nodeName) { // Row $rowHeight = $xmlReader->getAttribute('w:val', $tblNode, 'w:trPr/w:trHeight'); $rowHRule = $xmlReader->getAttribute('w:hRule', $tblNode, 'w:trPr/w:trHeight'); - $rowHRule = $rowHRule == 'exact' ? true : false; - $rowStyle = array( + $rowHRule = $rowHRule == 'exact'; + $rowStyle = [ 'tblHeader' => $xmlReader->elementExists('w:trPr/w:tblHeader', $tblNode), 'cantSplit' => $xmlReader->elementExists('w:trPr/w:cantSplit', $tblNode), 'exactHeight' => $rowHRule, - ); + ]; $row = $table->addRow($rowHeight, $rowStyle); $rowNodes = $xmlReader->getElements('*', $tblNode); foreach ($rowNodes as $rowNode) { - if ($rowNode->nodeName == 'w:trPr') { // Row style + if ('w:trPr' == $rowNode->nodeName) { // Row style // @todo Do something with row style - - } elseif ($rowNode->nodeName == 'w:tc') { // Cell + } 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 (!is_null($cellStyleNode)) { - $cellStyle = $this->readCellStyle($xmlReader, $cellStyleNode); + if ($xmlReader->elementExists('w:tcPr', $rowNode)) { + $cellStyle = $this->readCellStyle($xmlReader, $rowNode); } $cell = $row->addCell($cellWidth, $cellStyle); $cellNodes = $xmlReader->getElements('*', $rowNode); foreach ($cellNodes as $cellNode) { - if ($cellNode->nodeName == 'w:p') { // Paragraph + if ('w:p' == $cellNode->nodeName) { // Paragraph $this->readParagraph($xmlReader, $cellNode, $cell, $docPart); + } elseif ($cellNode->nodeName == 'w:tbl') { // Table + $this->readTable($xmlReader, $cellNode, $cell, $docPart); } } } @@ -307,49 +690,63 @@ protected function readTable(XMLReader $xmlReader, \DOMElement $domNode, $parent /** * Read w:pPr. * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $domNode - * @return array|null + * @return null|array */ - protected function readParagraphStyle(XMLReader $xmlReader, \DOMElement $domNode) + protected function readParagraphStyle(XMLReader $xmlReader, DOMElement $domNode) { if (!$xmlReader->elementExists('w:pPr', $domNode)) { return null; } $styleNode = $xmlReader->getElement('w:pPr', $domNode); - $styleDefs = array( - 'styleName' => array(self::READ_VALUE, 'w:pStyle'), - 'align' => array(self::READ_VALUE, 'w:jc'), - 'basedOn' => array(self::READ_VALUE, 'w:basedOn'), - 'next' => array(self::READ_VALUE, 'w:next'), - 'indent' => array(self::READ_VALUE, 'w:ind', 'w:left'), - 'hanging' => array(self::READ_VALUE, 'w:ind', 'w:hanging'), - 'spaceAfter' => array(self::READ_VALUE, 'w:spacing', 'w:after'), - 'spaceBefore' => array(self::READ_VALUE, 'w:spacing', 'w:before'), - 'widowControl' => array(self::READ_FALSE, 'w:widowControl'), - 'keepNext' => array(self::READ_TRUE, 'w:keepNext'), - 'keepLines' => array(self::READ_TRUE, 'w:keepLines'), - 'pageBreakBefore' => array(self::READ_TRUE, 'w:pageBreakBefore'), - ); + $styleDefs = [ + 'styleName' => [self::READ_VALUE, ['w:pStyle', 'w:name']], + 'alignment' => [self::READ_VALUE, 'w:jc'], + 'basedOn' => [self::READ_VALUE, 'w:basedOn'], + 'next' => [self::READ_VALUE, 'w:next'], + 'indentLeft' => [self::READ_VALUE, 'w:ind', 'w:left'], + 'indentRight' => [self::READ_VALUE, 'w:ind', 'w:right'], + 'indentHanging' => [self::READ_VALUE, 'w:ind', 'w:hanging'], + 'indentFirstLine' => [self::READ_VALUE, 'w:ind', 'w:firstLine'], + 'indentFirstLineChars' => [self::READ_VALUE, 'w:ind', 'w:firstLineChars'], + 'spaceAfter' => [self::READ_VALUE, 'w:spacing', 'w:after'], + 'spaceBefore' => [self::READ_VALUE, 'w:spacing', 'w:before'], + 'widowControl' => [self::READ_FALSE, 'w:widowControl'], + 'keepNext' => [self::READ_TRUE, 'w:keepNext'], + 'keepLines' => [self::READ_TRUE, 'w:keepLines'], + 'pageBreakBefore' => [self::READ_TRUE, 'w:pageBreakBefore'], + '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); } /** - * Read w:rPr + * Read w:rPr. * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $domNode - * @return array|null + * @return null|array */ - protected function readFontStyle(XMLReader $xmlReader, \DOMElement $domNode) + protected function readFontStyle(XMLReader $xmlReader, DOMElement $domNode) { - if (is_null($domNode)) { + if (null === $domNode) { return null; } // Hyperlink has an extra w:r child - if ($domNode->nodeName == 'w:hyperlink') { + if ('w:hyperlink' == $domNode->nodeName) { $domNode = $xmlReader->getElement('w:r', $domNode); } if (!$xmlReader->elementExists('w:rPr', $domNode)) { @@ -357,59 +754,74 @@ protected function readFontStyle(XMLReader $xmlReader, \DOMElement $domNode) } $styleNode = $xmlReader->getElement('w:rPr', $domNode); - $styleDefs = array( - 'styleName' => array(self::READ_VALUE, 'w:rStyle'), - 'name' => array(self::READ_VALUE, 'w:rFonts', 'w:ascii'), - 'hint' => array(self::READ_VALUE, 'w:rFonts', 'w:hint'), - 'size' => array(self::READ_SIZE, 'w:sz'), - 'color' => array(self::READ_VALUE, 'w:color'), - 'underline' => array(self::READ_VALUE, 'w:u'), - 'bold' => array(self::READ_TRUE, 'w:b'), - 'italic' => array(self::READ_TRUE, 'w:i'), - 'strikethrough' => array(self::READ_TRUE, 'w:strike'), - 'doubleStrikethrough' => array(self::READ_TRUE, 'w:dstrike'), - 'smallCaps' => array(self::READ_TRUE, 'w:smallCaps'), - 'allCaps' => array(self::READ_TRUE, 'w:caps'), - 'superScript' => array(self::READ_EQUAL, 'w:vertAlign', 'w:val', 'superscript'), - 'subScript' => array(self::READ_EQUAL, 'w:vertAlign', 'w:val', 'subscript'), - 'fgColor' => array(self::READ_VALUE, 'w:highlight'), - 'rtl' => array(self::READ_TRUE, 'w:rtl'), - ); + $styleDefs = [ + 'styleName' => [self::READ_VALUE, 'w:rStyle'], + 'name' => [self::READ_VALUE, 'w:rFonts', ['w:ascii', 'w:hAnsi', 'w:eastAsia', 'w:cs']], + 'hint' => [self::READ_VALUE, 'w:rFonts', 'w:hint'], + 'size' => [self::READ_SIZE, ['w:sz', 'w:szCs']], + 'color' => [self::READ_VALUE, 'w:color'], + 'underline' => [self::READ_VALUE, 'w:u'], + 'bold' => [self::READ_TRUE, 'w:b'], + 'italic' => [self::READ_TRUE, 'w:i'], + 'strikethrough' => [self::READ_TRUE, 'w:strike'], + 'doubleStrikethrough' => [self::READ_TRUE, 'w:dstrike'], + 'smallCaps' => [self::READ_TRUE, 'w:smallCaps'], + 'allCaps' => [self::READ_TRUE, 'w:caps'], + 'superScript' => [self::READ_EQUAL, 'w:vertAlign', 'w:val', 'superscript'], + 'subScript' => [self::READ_EQUAL, 'w:vertAlign', 'w:val', 'subscript'], + 'fgColor' => [self::READ_VALUE, 'w:highlight'], + 'rtl' => [self::READ_TRUE, 'w:rtl'], + 'lang' => [self::READ_VALUE, 'w:lang'], + 'position' => [self::READ_VALUE, 'w:position'], + 'hidden' => [self::READ_TRUE, 'w:vanish'], + ]; return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); } /** - * Read w:tblPr + * Read w:tblPr. + * + * @return null|array|string * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $domNode - * @return string|array|null * @todo Capture w:tblStylePr w:type="firstRow" */ - protected function readTableStyle(XMLReader $xmlReader, \DOMElement $domNode) + protected function readTableStyle(XMLReader $xmlReader, DOMElement $domNode) { $style = null; - $margins = array('top', 'left', 'bottom', 'right'); - $borders = $margins + array('insideH', 'insideV'); + $margins = ['top', 'left', 'bottom', 'right']; + $borders = array_merge($margins, ['insideH', 'insideV']); if ($xmlReader->elementExists('w:tblPr', $domNode)) { if ($xmlReader->elementExists('w:tblPr/w:tblStyle', $domNode)) { $style = $xmlReader->getAttribute('w:val', $domNode, 'w:tblPr/w:tblStyle'); } else { $styleNode = $xmlReader->getElement('w:tblPr', $domNode); - $styleDefs = array(); - // $styleDefs['styleName'] = array(self::READ_VALUE, 'w:tblStyle'); + $styleDefs = []; foreach ($margins as $side) { $ucfSide = ucfirst($side); - $styleDefs["cellMargin$ucfSide"] = array(self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w'); + $styleDefs["cellMargin$ucfSide"] = [self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w']; } foreach ($borders as $side) { $ucfSide = ucfirst($side); - $styleDefs["border{$ucfSide}Size"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz'); - $styleDefs["border{$ucfSide}Color"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:color'); + $styleDefs["border{$ucfSide}Size"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz']; + $styleDefs["border{$ucfSide}Color"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:color']; + $styleDefs["border{$ucfSide}Style"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:val']; } + $styleDefs['layout'] = [self::READ_VALUE, 'w:tblLayout', 'w:type']; + $styleDefs['bidiVisual'] = [self::READ_TRUE, 'w:bidiVisual']; + $styleDefs['cellSpacing'] = [self::READ_VALUE, 'w:tblCellSpacing', 'w:w']; $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); + + $tablePositionNode = $xmlReader->getElement('w:tblpPr', $styleNode); + if ($tablePositionNode !== null) { + $style['position'] = $this->readTablePosition($xmlReader, $tablePositionNode); + } + + $indentNode = $xmlReader->getElement('w:tblInd', $styleNode); + if ($indentNode !== null) { + $style['indent'] = $this->readTableIndent($xmlReader, $indentNode); + } } } @@ -417,47 +829,154 @@ protected function readTableStyle(XMLReader $xmlReader, \DOMElement $domNode) } /** - * Read w:tcPr + * Read w:tblpPr. * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $domNode * @return array */ - private function readCellStyle(XMLReader $xmlReader, \DOMElement $domNode) + private function readTablePosition(XMLReader $xmlReader, DOMElement $domNode) { - $styleDefs = array( - 'valign' => array(self::READ_VALUE, 'w:vAlign'), - 'textDirection' => array(self::READ_VALUE, 'w:textDirection'), - 'gridSpan' => array(self::READ_VALUE, 'w:gridSpan'), - 'vMerge' => array(self::READ_VALUE, 'w:vMerge'), - 'bgColor' => array(self::READ_VALUE, 'w:shd/w:fill'), - ); + $styleDefs = [ + 'leftFromText' => [self::READ_VALUE, '.', 'w:leftFromText'], + 'rightFromText' => [self::READ_VALUE, '.', 'w:rightFromText'], + 'topFromText' => [self::READ_VALUE, '.', 'w:topFromText'], + 'bottomFromText' => [self::READ_VALUE, '.', 'w:bottomFromText'], + 'vertAnchor' => [self::READ_VALUE, '.', 'w:vertAnchor'], + 'horzAnchor' => [self::READ_VALUE, '.', 'w:horzAnchor'], + 'tblpXSpec' => [self::READ_VALUE, '.', 'w:tblpXSpec'], + 'tblpX' => [self::READ_VALUE, '.', 'w:tblpX'], + 'tblpYSpec' => [self::READ_VALUE, '.', 'w:tblpYSpec'], + 'tblpY' => [self::READ_VALUE, '.', 'w:tblpY'], + ]; return $this->readStyleDefs($xmlReader, $domNode, $styleDefs); } /** - * Read style definition + * Read w:tblInd. + * + * @return TblWidthComplexType + */ + private function readTableIndent(XMLReader $xmlReader, DOMElement $domNode) + { + $styleDefs = [ + 'value' => [self::READ_VALUE, '.', 'w:w'], + 'type' => [self::READ_VALUE, '.', 'w:type'], + ]; + $styleDefs = $this->readStyleDefs($xmlReader, $domNode, $styleDefs); + + return new TblWidthComplexType((int) $styleDefs['value'], $styleDefs['type']); + } + + /** + * Read w:tcPr. + * + * @return null|array + */ + private function readCellStyle(XMLReader $xmlReader, DOMElement $domNode) + { + $styleDefs = [ + 'valign' => [self::READ_VALUE, 'w:vAlign'], + 'textDirection' => [self::READ_VALUE, 'w:textDirection'], + 'gridSpan' => [self::READ_VALUE, 'w:gridSpan'], + '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; + + 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; + } + + /** + * Returns the first child element found. + * + * @param null|array|string $elements + * + * @return null|string + */ + private function findPossibleElement(XMLReader $xmlReader, ?DOMElement $parentNode = null, $elements = null) + { + if (is_array($elements)) { + //if element is an array, we take the first element that exists in the XML + foreach ($elements as $possibleElement) { + if ($xmlReader->elementExists($possibleElement, $parentNode)) { + return $possibleElement; + } + } + } else { + return $elements; + } + + return null; + } + + /** + * Returns the first attribute found. + * + * @param array|string $attributes + * + * @return null|string + */ + private function findPossibleAttribute(XMLReader $xmlReader, DOMElement $node, $attributes) + { + //if attribute is an array, we take the first attribute that exists in the XML + if (is_array($attributes)) { + foreach ($attributes as $possibleAttribute) { + if ($xmlReader->getAttribute($possibleAttribute, $node)) { + return $possibleAttribute; + } + } + + return null; + } + + return $attributes; + } + + /** + * Read style definition. * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $parentNode * @param array $styleDefs + * * @ignoreScrutinizerPatch + * * @return array */ - protected function readStyleDefs(XMLReader $xmlReader, \DOMElement $parentNode = null, $styleDefs = array()) + protected function readStyleDefs(XMLReader $xmlReader, ?DOMElement $parentNode = null, $styleDefs = []) { - $styles = array(); + $styles = []; foreach ($styleDefs as $styleProp => $styleVal) { - @list($method, $element, $attribute, $expected) = $styleVal; + [$method, $element, $attribute, $expected, $default] = array_pad($styleVal, 5, null); + + $element = $this->findPossibleElement($xmlReader, $parentNode, $element); + if ($element === null) { + continue; + } if ($xmlReader->elementExists($element, $parentNode)) { $node = $xmlReader->getElement($element, $parentNode); + $attribute = $this->findPossibleAttribute($xmlReader, $node, $attribute); + // 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) { @@ -470,25 +989,28 @@ protected function readStyleDefs(XMLReader $xmlReader, \DOMElement $parentNode = } /** - * Return style definition based on conversion method + * Return style definition based on conversion method. * * @param string $method + * * @ignoreScrutinizerPatch - * @param mixed $attributeValue + * + * @param null|string $attributeValue * @param mixed $expected + * * @return mixed */ private function readStyleDef($method, $attributeValue, $expected) { $style = $attributeValue; - if ($method == self::READ_SIZE) { + if (self::READ_SIZE == $method) { $style = $attributeValue / 2; - } elseif ($method == self::READ_TRUE) { - $style = true; - } elseif ($method == self::READ_FALSE) { - $style = false; - } elseif ($method == self::READ_EQUAL) { + } elseif (self::READ_TRUE == $method) { + $style = $this->isOn($attributeValue); + } elseif (self::READ_FALSE == $method) { + $style = !$this->isOn($attributeValue); + } elseif (self::READ_EQUAL == $method) { $style = $attributeValue == $expected; } @@ -496,20 +1018,54 @@ private function readStyleDef($method, $attributeValue, $expected) } /** - * Returns the target of image, object, or link as stored in ::readMainRels + * Parses the value of the on/off value, null is considered true as it means the w:val attribute was not present. + * + * @see http://www.datypic.com/sc/ooxml/t-w_ST_OnOff.html + * + * @param string $value + * + * @return bool + */ + private function isOn($value = null) + { + return $value === null || $value === '1' || $value === 'true' || $value === 'on'; + } + + /** + * Returns the target of image, object, or link as stored in ::readMainRels. * * @param string $docPart * @param string $rId - * @return string|null + * + * @return null|string */ private function getMediaTarget($docPart, $rId) { $target = null; - if (isset($this->rels[$docPart]) && isset($this->rels[$docPart][$rId])) { + if (isset($this->rels[$docPart], $this->rels[$docPart][$rId])) { $target = $this->rels[$docPart][$rId]['target']; } return $target; } + + /** + * Returns the target mode. + * + * @param string $docPart + * @param string $rId + * + * @return null|string + */ + private function getTargetMode($docPart, $rId) + { + $mode = null; + + if (isset($this->rels[$docPart], $this->rels[$docPart][$rId])) { + $mode = $this->rels[$docPart][$rId]['targetMode']; + } + + return $mode; + } } 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 @@ +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/DocPropsApp.php b/src/PhpWord/Reader/Word2007/DocPropsApp.php index ddbe474f1a..c7ecb007b3 100644 --- a/src/PhpWord/Reader/Word2007/DocPropsApp.php +++ b/src/PhpWord/Reader/Word2007/DocPropsApp.php @@ -1,4 +1,5 @@ 'setCompany', 'Manager' => 'setManager'); + protected $mapping = ['Company' => 'setCompany', 'Manager' => 'setManager']; /** - * Callback functions + * Callback functions. * * @var array */ - protected $callbacks = array(); + protected $callbacks = []; } diff --git a/src/PhpWord/Reader/Word2007/DocPropsCore.php b/src/PhpWord/Reader/Word2007/DocPropsCore.php index 54537525b6..2458bb84cf 100644 --- a/src/PhpWord/Reader/Word2007/DocPropsCore.php +++ b/src/PhpWord/Reader/Word2007/DocPropsCore.php @@ -1,4 +1,5 @@ 'setCreator', 'dc:title' => 'setTitle', 'dc:description' => 'setDescription', @@ -42,22 +43,19 @@ class DocPropsCore extends AbstractPart 'cp:lastModifiedBy' => 'setLastModifiedBy', 'dcterms:created' => 'setCreated', 'dcterms:modified' => 'setModified', - ); + ]; /** - * Callback functions + * Callback functions. * * @var array */ - protected $callbacks = array('dcterms:created' => 'strtotime', 'dcterms:modified' => 'strtotime'); + protected $callbacks = ['dcterms:created' => 'strtotime', 'dcterms:modified' => 'strtotime']; /** * Read core/extended document properties. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord - * @return void */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); diff --git a/src/PhpWord/Reader/Word2007/DocPropsCustom.php b/src/PhpWord/Reader/Word2007/DocPropsCustom.php index eb725b2ec3..442017199b 100644 --- a/src/PhpWord/Reader/Word2007/DocPropsCustom.php +++ b/src/PhpWord/Reader/Word2007/DocPropsCustom.php @@ -1,4 +1,5 @@ getDomFromZip($this->docFile, $this->xmlFile); diff --git a/src/PhpWord/Reader/Word2007/Document.php b/src/PhpWord/Reader/Word2007/Document.php index e1beed0666..89e479ef36 100644 --- a/src/PhpWord/Reader/Word2007/Document.php +++ b/src/PhpWord/Reader/Word2007/Document.php @@ -1,4 +1,5 @@ phpWord = $phpWord; $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); - $readMethods = array('w:p' => 'readWPNode', 'w:tbl' => 'readTable', 'w:sectPr' => 'readWSectPrNode'); + $readMethods = ['w:p' => 'readWPNode', 'w:tbl' => 'readTable', 'w:sectPr' => 'readWSectPrNode']; $nodes = $xmlReader->getElements('w:body/*'); if ($nodes->length > 0) { @@ -65,17 +65,15 @@ public function read(PhpWord $phpWord) * Read header footer. * * @param array $settings - * @param \PhpOffice\PhpWord\Element\Section &$section - * @return void */ - private function readHeaderFooter($settings, Section &$section) + private function readHeaderFooter($settings, Section &$section): void { - $readMethods = array('w:p' => 'readParagraph', 'w:tbl' => 'readTable'); + $readMethods = ['w:p' => 'readParagraph', 'w:tbl' => 'readTable']; if (is_array($settings) && isset($settings['hf'])) { foreach ($settings['hf'] as $rId => $hfSetting) { if (isset($this->rels['document'][$rId])) { - list($hfType, $xmlFile, $docPart) = array_values($this->rels['document'][$rId]); + [$hfType, $xmlFile, $docPart] = array_values($this->rels['document'][$rId]); $addMethod = "add{$hfType}"; $hfObject = $section->$addMethod($hfSetting['type']); @@ -97,30 +95,30 @@ private function readHeaderFooter($settings, Section &$section) } /** - * Read w:sectPr + * Read w:sectPr. * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $domNode * @ignoreScrutinizerPatch + * * @return array */ - private function readSectionStyle(XMLReader $xmlReader, \DOMElement $domNode) + private function readSectionStyle(XMLReader $xmlReader, DOMElement $domNode) { - $styleDefs = array( - 'breakType' => array(self::READ_VALUE, 'w:type'), - 'pageSizeW' => array(self::READ_VALUE, 'w:pgSz', 'w:w'), - 'pageSizeH' => array(self::READ_VALUE, 'w:pgSz', 'w:h'), - 'orientation' => array(self::READ_VALUE, 'w:pgSz', 'w:orient'), - 'colsNum' => array(self::READ_VALUE, 'w:cols', 'w:num'), - 'colsSpace' => array(self::READ_VALUE, 'w:cols', 'w:space'), - 'topMargin' => array(self::READ_VALUE, 'w:pgMar', 'w:top'), - 'leftMargin' => array(self::READ_VALUE, 'w:pgMar', 'w:left'), - 'bottomMargin' => array(self::READ_VALUE, 'w:pgMar', 'w:bottom'), - 'rightMargin' => array(self::READ_VALUE, 'w:pgMar', 'w:right'), - 'headerHeight' => array(self::READ_VALUE, 'w:pgMar', 'w:header'), - 'footerHeight' => array(self::READ_VALUE, 'w:pgMar', 'w:footer'), - 'gutter' => array(self::READ_VALUE, 'w:pgMar', 'w:gutter'), - ); + $styleDefs = [ + 'breakType' => [self::READ_VALUE, 'w:type'], + 'vAlign' => [self::READ_VALUE, 'w:vAlign'], + 'pageSizeW' => [self::READ_VALUE, 'w:pgSz', 'w:w'], + 'pageSizeH' => [self::READ_VALUE, 'w:pgSz', 'w:h'], + 'orientation' => [self::READ_VALUE, 'w:pgSz', 'w:orient'], + 'colsNum' => [self::READ_VALUE, 'w:cols', 'w:num'], + 'colsSpace' => [self::READ_VALUE, 'w:cols', 'w:space'], + 'marginTop' => [self::READ_VALUE, 'w:pgMar', 'w:top'], + 'marginLeft' => [self::READ_VALUE, 'w:pgMar', 'w:left'], + 'marginBottom' => [self::READ_VALUE, 'w:pgMar', 'w:bottom'], + 'marginRight' => [self::READ_VALUE, 'w:pgMar', 'w:right'], + 'headerHeight' => [self::READ_VALUE, 'w:pgMar', 'w:header'], + 'footerHeight' => [self::READ_VALUE, 'w:pgMar', 'w:footer'], + 'gutter' => [self::READ_VALUE, 'w:pgMar', 'w:gutter'], + ]; $styles = $this->readStyleDefs($xmlReader, $domNode, $styleDefs); // Header and footer @@ -129,10 +127,10 @@ private function readSectionStyle(XMLReader $xmlReader, \DOMElement $domNode) foreach ($nodes as $node) { if ($node->nodeName == 'w:headerReference' || $node->nodeName == 'w:footerReference') { $id = $xmlReader->getAttribute('r:id', $node); - $styles['hf'][$id] = array( + $styles['hf'][$id] = [ 'method' => str_replace('w:', '', str_replace('Reference', '', $node->nodeName)), 'type' => $xmlReader->getAttribute('w:type', $node), - ); + ]; } } @@ -141,15 +139,8 @@ private function readSectionStyle(XMLReader $xmlReader, \DOMElement $domNode) /** * Read w:p node. - * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $node - * @param \PhpOffice\PhpWord\Element\Section &$section - * @return void - * - * @todo */ - private function readWPNode(XMLReader $xmlReader, \DOMElement $node, Section &$section) + private function readWPNode(XMLReader $xmlReader, DOMElement $node, Section &$section): void { // Page break if ($xmlReader->getAttribute('w:type', $node, 'w:r/w:br') == 'page') { @@ -171,13 +162,8 @@ private function readWPNode(XMLReader $xmlReader, \DOMElement $node, Section &$s /** * Read w:sectPr node. - * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $node - * @param \PhpOffice\PhpWord\Element\Section &$section - * @return void */ - private function readWSectPrNode(XMLReader $xmlReader, \DOMElement $node, Section &$section) + private function readWSectPrNode(XMLReader $xmlReader, DOMElement $node, Section &$section): void { $style = $this->readSectionStyle($xmlReader, $node); $section->setStyle($style); diff --git a/src/PhpWord/Reader/Word2007/Endnotes.php b/src/PhpWord/Reader/Word2007/Endnotes.php index c493c34790..6ab4203579 100644 --- a/src/PhpWord/Reader/Word2007/Endnotes.php +++ b/src/PhpWord/Reader/Word2007/Endnotes.php @@ -1,4 +1,5 @@ collection}"; - $collection = $phpWord->$getMethod()->getItems(); - $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); $nodes = $xmlReader->getElements('*'); @@ -61,17 +56,41 @@ public function read(PhpWord $phpWord) $type = $xmlReader->getAttribute('w:type', $node); // Avoid w:type "separator" and "continuationSeparator" - // Only look for or without w:type attribute - if (is_null($type) && isset($collection[$id])) { - $element = $collection[$id]; - $pNodes = $xmlReader->getElements('w:p/*', $node); - foreach ($pNodes as $pNode) { - $this->readRun($xmlReader, $pNode, $element, $this->collection); + // Only look for or without w:type attribute, or with w:type = normal + if ((null === $type || $type === 'normal')) { + $element = $this->getElement($phpWord, $id); + if ($element !== null) { + $pNodes = $xmlReader->getElements('w:p/*', $node); + foreach ($pNodes as $pNode) { + $this->readRun($xmlReader, $pNode, $element, $this->collection); + } + $addMethod = "add{$this->element}"; + $phpWord->$addMethod($element); } - $addMethod = "add{$this->element}"; - $phpWord->$addMethod($element); } } } } + + /** + * Searches for the element with the given relationId. + * + * @param int $relationId + * + * @return null|\PhpOffice\PhpWord\Element\AbstractContainer + */ + private function getElement(PhpWord $phpWord, $relationId) + { + $getMethod = "get{$this->collection}"; + $collection = $phpWord->$getMethod()->getItems(); + + //not found by key, looping to search by relationId + foreach ($collection as $collectionElement) { + if ($collectionElement->getRelationId() == $relationId) { + return $collectionElement; + } + } + + return null; + } } diff --git a/src/PhpWord/Reader/Word2007/Numbering.php b/src/PhpWord/Reader/Word2007/Numbering.php index 872d45033b..564c43b23b 100644 --- a/src/PhpWord/Reader/Word2007/Numbering.php +++ b/src/PhpWord/Reader/Word2007/Numbering.php @@ -1,4 +1,5 @@ getDomFromZip($this->docFile, $this->xmlFile); @@ -45,17 +44,19 @@ public function read(PhpWord $phpWord) if ($nodes->length > 0) { foreach ($nodes as $node) { $abstractId = $xmlReader->getAttribute('w:abstractNumId', $node); - $abstracts[$abstractId] = array('levels' => array()); + $abstracts[$abstractId] = ['levels' => []]; $abstract = &$abstracts[$abstractId]; $subnodes = $xmlReader->getElements('*', $node); foreach ($subnodes as $subnode) { switch ($subnode->nodeName) { case 'w:multiLevelType': $abstract['type'] = $xmlReader->getAttribute('w:val', $subnode); + break; case 'w:lvl': $levelId = $xmlReader->getAttribute('w:ilvl', $subnode); $abstract['levels'][$levelId] = $this->readLevel($xmlReader, $subnode, $levelId); + break; } } @@ -88,16 +89,15 @@ public function read(PhpWord $phpWord) } /** - * Read numbering level definition from w:abstractNum and w:num + * Read numbering level definition from w:abstractNum and w:num. + * + * @param int $levelId * - * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader - * @param \DOMElement $subnode - * @param integer $levelId * @return array */ - private function readLevel(XMLReader $xmlReader, \DOMElement $subnode, $levelId) + private function readLevel(XMLReader $xmlReader, DOMElement $subnode, $levelId) { - $level = array(); + $level = []; $level['level'] = $levelId; $level['start'] = $xmlReader->getAttribute('w:val', $subnode, 'w:start'); @@ -105,7 +105,7 @@ private function readLevel(XMLReader $xmlReader, \DOMElement $subnode, $levelId) $level['restart'] = $xmlReader->getAttribute('w:val', $subnode, 'w:lvlRestart'); $level['suffix'] = $xmlReader->getAttribute('w:val', $subnode, 'w:suff'); $level['text'] = $xmlReader->getAttribute('w:val', $subnode, 'w:lvlText'); - $level['align'] = $xmlReader->getAttribute('w:val', $subnode, 'w:lvlJc'); + $level['alignment'] = $xmlReader->getAttribute('w:val', $subnode, 'w:lvlJc'); $level['tab'] = $xmlReader->getAttribute('w:pos', $subnode, 'w:pPr/w:tabs/w:tab'); $level['left'] = $xmlReader->getAttribute('w:left', $subnode, 'w:pPr/w:ind'); $level['hanging'] = $xmlReader->getAttribute('w:hanging', $subnode, 'w:pPr/w:ind'); @@ -113,7 +113,7 @@ private function readLevel(XMLReader $xmlReader, \DOMElement $subnode, $levelId) $level['hint'] = $xmlReader->getAttribute('w:hint', $subnode, 'w:rPr/w:rFonts'); foreach ($level as $key => $value) { - if (is_null($value)) { + if (null === $value) { unset($level[$key]); } } diff --git a/src/PhpWord/Reader/Word2007/Settings.php b/src/PhpWord/Reader/Word2007/Settings.php new file mode 100644 index 0000000000..f4f8be6fb2 --- /dev/null +++ b/src/PhpWord/Reader/Word2007/Settings.php @@ -0,0 +1,171 @@ + + */ + private $booleanProperties = [ + 'mirrorMargins', + 'hideSpellingErrors', + 'hideGrammaticalErrors', + 'trackRevisions', + 'doNotTrackMoves', + 'doNotTrackFormatting', + 'evenAndOddHeaders', + 'updateFields', + 'autoHyphenation', + 'doNotHyphenateCaps', + 'bookFoldPrinting', + ]; + + /** + * Read settings.xml. + */ + public function read(PhpWord $phpWord): void + { + $xmlReader = new XMLReader(); + $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); + + $docSettings = $phpWord->getSettings(); + + $nodes = $xmlReader->getElements('*'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $name = str_replace('w:', '', $node->nodeName); + $value = $xmlReader->getAttribute('w:val', $node); + $method = 'set' . $name; + + 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)) { + $docSettings->$method($value); + } + } + } + } + + /** + * Sets the document Language. + */ + protected function setThemeFontLang(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void + { + $val = $xmlReader->getAttribute('w:val', $node); + $eastAsia = $xmlReader->getAttribute('w:eastAsia', $node); + $bidi = $xmlReader->getAttribute('w:bidi', $node); + + $themeFontLang = new Language(); + $themeFontLang->setLatin($val); + $themeFontLang->setEastAsia($eastAsia); + $themeFontLang->setBidirectional($bidi); + + $phpWord->getSettings()->setThemeFontLang($themeFontLang); + } + + /** + * Sets the document protection. + */ + protected function setDocumentProtection(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void + { + $documentProtection = $phpWord->getSettings()->getDocumentProtection(); + + $edit = $xmlReader->getAttribute('w:edit', $node); + if ($edit !== null) { + $documentProtection->setEditing($edit); + } + } + + /** + * Sets the proof state. + */ + protected function setProofState(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void + { + $proofState = $phpWord->getSettings()->getProofState(); + + $spelling = $xmlReader->getAttribute('w:spelling', $node); + $grammar = $xmlReader->getAttribute('w:grammar', $node); + + if ($spelling !== null) { + $proofState->setSpelling($spelling); + } + if ($grammar !== null) { + $proofState->setGrammar($grammar); + } + } + + /** + * Sets the proof state. + */ + protected function setZoom(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void + { + $percent = $xmlReader->getAttribute('w:percent', $node); + $val = $xmlReader->getAttribute('w:val', $node); + + if ($percent !== null || $val !== null) { + $phpWord->getSettings()->setZoom($percent === null ? $val : $percent); + } + } + + /** + * Set the Revision view. + */ + protected function setRevisionView(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void + { + $revisionView = new TrackChangesView(); + $revisionView->setMarkup(filter_var($xmlReader->getAttribute('w:markup', $node), FILTER_VALIDATE_BOOLEAN)); + $revisionView->setComments($xmlReader->getAttribute('w:comments', $node)); + $revisionView->setInsDel(filter_var($xmlReader->getAttribute('w:insDel', $node), FILTER_VALIDATE_BOOLEAN)); + $revisionView->setFormatting(filter_var($xmlReader->getAttribute('w:formatting', $node), FILTER_VALIDATE_BOOLEAN)); + $revisionView->setInkAnnotations(filter_var($xmlReader->getAttribute('w:inkAnnotations', $node), FILTER_VALIDATE_BOOLEAN)); + $phpWord->getSettings()->setRevisionView($revisionView); + } + + protected function setConsecutiveHyphenLimit(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void + { + $value = $xmlReader->getAttribute('w:val', $node); + + if ($value !== null) { + $phpWord->getSettings()->setConsecutiveHyphenLimit($value); + } + } + + protected function setHyphenationZone(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void + { + $value = $xmlReader->getAttribute('w:val', $node); + + if ($value !== null) { + $phpWord->getSettings()->setHyphenationZone($value); + } + } +} diff --git a/src/PhpWord/Reader/Word2007/Styles.php b/src/PhpWord/Reader/Word2007/Styles.php index 299fe1df77..d0777c3026 100644 --- a/src/PhpWord/Reader/Word2007/Styles.php +++ b/src/PhpWord/Reader/Word2007/Styles.php @@ -1,4 +1,5 @@ getDomFromZip($this->docFile, $this->xmlFile); + $fontDefaults = $xmlReader->getElement('w:docDefaults/w:rPrDefault'); + if ($fontDefaults !== null) { + $fontDefaultStyle = $this->readFontStyle($xmlReader, $fontDefaults); + 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('color', $fontDefaultStyle)) { + $phpWord->setDefaultFontColor($fontDefaultStyle['color']); + } + if (array_key_exists('lang', $fontDefaultStyle)) { + $phpWord->getSettings()->setThemeFontLang(new Language($fontDefaultStyle['lang'])); + } + } + } + + $paragraphDefaults = $xmlReader->getElement('w:docDefaults/w:pPrDefault'); + if ($paragraphDefaults !== null) { + $paragraphDefaultStyle = $this->readParagraphStyle($xmlReader, $paragraphDefaults); + if ($paragraphDefaultStyle != null) { + $phpWord->setDefaultParagraphStyle($paragraphDefaultStyle); + } + } + $nodes = $xmlReader->getElements('w:style'); if ($nodes->length > 0) { foreach ($nodes as $node) { $type = $xmlReader->getAttribute('w:type', $node); - $name = $xmlReader->getAttribute('w:styleId', $node); - if (is_null($name)) { - $name = $xmlReader->getAttribute('w:val', $node, 'w:name'); + $name = $xmlReader->getAttribute('w:val', $node, 'w:name'); + if (null === $name) { + $name = $xmlReader->getAttribute('w:styleId', $node); } - preg_match('/Heading(\d)/', $name, $headingMatches); + $headingMatches = []; + preg_match('/Heading\s*(\d)/i', $name, $headingMatches); // $default = ($xmlReader->getAttribute('w:default', $node) == 1); switch ($type) { - case 'paragraph': $paragraphStyle = $this->readParagraphStyle($xmlReader, $node); $fontStyle = $this->readFontStyle($xmlReader, $node); @@ -64,20 +90,21 @@ public function read(PhpWord $phpWord) $phpWord->addFontStyle($name, $fontStyle, $paragraphStyle); } } - break; + break; case 'character': $fontStyle = $this->readFontStyle($xmlReader, $node); if (!empty($fontStyle)) { $phpWord->addFontStyle($name, $fontStyle); } - break; + break; case 'table': $tStyle = $this->readTableStyle($xmlReader, $node); if (!empty($tStyle)) { $phpWord->addTableStyle($name, $tStyle); } + break; } } diff --git a/src/PhpWord/Settings.php b/src/PhpWord/Settings.php index 67b1dbedbd..16f49166fa 100644 --- a/src/PhpWord/Settings.php +++ b/src/PhpWord/Settings.php @@ -1,124 +1,158 @@ 0) { + if ((is_int($value) || is_float($value)) && (int) $value > 0) { self::$defaultFontSize = $value; + return true; } 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 + * Load setting from phpword.yml or phpword.yml.dist. */ - public static function loadConfig($filename = null) + public static function loadConfig(?string $filename = null): array { // Get config file $configFile = null; $configPath = __DIR__ . '/../../'; if ($filename !== null) { - $files = array($filename); + $files = [$filename]; } else { - $files = array("{$configPath}phpword.ini", "{$configPath}phpword.ini.dist"); + $files = ["{$configPath}phpword.ini", "{$configPath}phpword.ini.dist"]; } foreach ($files as $file) { if (file_exists($file)) { $configFile = realpath($file); + break; } } // Parse config file - $config = array(); + $config = []; if ($configFile !== null) { $config = @parse_ini_file($configFile); if ($config === false) { - return $config; + return []; } } // Set config value + $appliedConfig = []; foreach ($config as $key => $value) { $method = "set{$key}"; if (method_exists(__CLASS__, $method)) { self::$method($value); + $appliedConfig[$key] = $value; } } - return $config; + return $appliedConfig; } /** - * Return the compatibility option used by the XMLWriter - * - * @deprecated 0.10.0 - * @codeCoverageIgnore + * Get default paper. */ - public static function getCompatibility() + public static function getDefaultPaper(): string { - return self::hasCompatibility(); + return self::$defaultPaper; + } + + /** + * Set default paper. + */ + public static function setDefaultPaper(string $value): bool + { + if (trim($value) !== '') { + self::$defaultPaper = $value; + + return true; + } + + return false; } } diff --git a/src/PhpWord/Shared/AbstractEnum.php b/src/PhpWord/Shared/AbstractEnum.php new file mode 100644 index 0000000000..cfbcf1e8a6 --- /dev/null +++ b/src/PhpWord/Shared/AbstractEnum.php @@ -0,0 +1,80 @@ +getConstants(); + } + + return self::$constCacheArray[$calledClass]; + } + + /** + * Returns all values for this enum. + * + * @return array + */ + public static function values() + { + return array_values(self::getConstants()); + } + + /** + * Returns true the value is valid for this enum. + * + * @param string $value + * + * @return bool true if value is valid + */ + public static function isValid($value) + { + $values = array_values(self::getConstants()); + + return in_array($value, $values, true); + } + + /** + * Validates that the value passed is a valid value. + * + * @param string $value + */ + public static function validate($value): void + { + if (!self::isValid($value)) { + $calledClass = static::class; + $values = array_values(self::getConstants()); + + throw new InvalidArgumentException("$value is not a valid value for $calledClass, possible values are " . implode(', ', $values)); + } + } +} diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index c6727edd55..17d2e1a05d 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -1,4 +1,5 @@ > + */ + 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 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/Drawing.php b/src/PhpWord/Shared/Drawing.php index e3a33e5b69..8af7da2ffc 100644 --- a/src/PhpWord/Shared/Drawing.php +++ b/src/PhpWord/Shared/Drawing.php @@ -1,50 +1,64 @@ . * - * @param \PhpOffice\PhpWord\Element\AbstractContainer $element Where the parts need to be added + * @param AbstractContainer $element Where the parts need to be added * @param string $html The code to parse * @param bool $fullHTML If it's a full HTML, no need to add 'body' tag - * @return void + * @param bool $preserveWhiteSpace If false, the whitespaces between nodes will be removed */ - public static function addHtml($element, $html, $fullHTML = false) + public static function addHtml($element, $html, $fullHTML = false, $preserveWhiteSpace = true, $options = null): void { /* * @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. */ + static::$options = $options; // Preprocess: remove all line ends, decode HTML entity, // fix ampersand and angle brackets and add body tag for HTML fragments - $html = str_replace(array("\n", "\r"), '', $html); - $html = str_replace(array('<', '>', '&'), array('_lt_', '_gt_', '_amp_'), $html); + $html = str_replace(["\n", "\r"], '', $html); + $html = str_replace(['<', '>', '&', '"'], ['_lt_', '_gt_', '_amp_', '_quot_'], $html); $html = html_entity_decode($html, ENT_QUOTES, 'UTF-8'); $html = str_replace('&', '&', $html); - $html = str_replace(array('_lt_', '_gt_', '_amp_'), array('<', '>', '&'), $html); + $html = str_replace(['_lt_', '_gt_', '_amp_', '_quot_'], ['<', '>', '&', '"'], $html); - if ($fullHTML === false) { + if (false === $fullHTML) { $html = '' . $html . ''; } // Load DOM - $dom = new \DOMDocument(); - $dom->preserveWhiteSpace = true; + if (\PHP_VERSION_ID < 80000) { + $orignalLibEntityLoader = libxml_disable_entity_loader(true); + } + $dom = new DOMDocument(); + $dom->preserveWhiteSpace = $preserveWhiteSpace; $dom->loadXML($html); + 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); + } } /** - * parse Inline style of a node + * 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 \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 * @return array */ - protected static function parseInlineStyle($node, $styles = array()) + protected static function parseInlineStyle($node, $styles = []) { - if ($node->nodeType == XML_ELEMENT_NODE) { + 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) { - switch ($attribute->name) { - case 'style': - $styles = self::parseStyle($attribute, $styles); + $val = $attribute->value; + switch (strtolower($attribute->name)) { + case 'align': + $styles['alignment'] = self::mapAlign(trim($val), $bidi); + + break; + case 'lang': + $styles['lang'] = $val; + + break; + case 'width': + // tables, cells + $val = $val === 'auto' ? '100%' : $val; + if (false !== strpos($val, '%')) { + // e.g. or + + + + + + + + +
    + $styles['width'] = (int) $val * 50; + $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT; + } else { + // e.g. , where "2" = 2px (always pixels) + $styles['cellSpacing'] = Converter::pixelToTwip(self::convertHtmlSize($val)); + + break; + case 'bgcolor': + // tables, rows, cells e.g. + $styles['bgColor'] = self::convertRgb($val); + + break; + case 'valign': + // cells e.g. + + + +
    + if (preg_match('#(?:top|bottom|middle|baseline)#i', $val, $matches)) { + $styles['valign'] = self::mapAlignVertical($matches[0]); + } + break; } } + + $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); + } } return $styles; @@ -91,61 +186,79 @@ protected static function parseInlineStyle($node, $styles = array()) /** * Parse a node and add a corresponding element to the parent element. * - * @param \DOMNode $node node to parse - * @param \PhpOffice\PhpWord\Element\AbstractContainer $element object to add an element corresponding with the node + * @param DOMNode $node node to parse + * @param AbstractContainer $element object to add an element corresponding with the node * @param array $styles Array with all styles * @param array $data Array to transport data to a next level in the DOM tree, for example level of listitems - * @return void */ - protected static function parseNode($node, $element, $styles = array(), $data = array()) + 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 = array('font', 'paragraph', 'list'); + $styleTypes = ['font', 'paragraph', 'list', 'table', 'row', 'cell']; foreach ($styleTypes as $styleType) { if (!isset($styles[$styleType])) { - $styles[$styleType] = array(); + $styles[$styleType] = []; } } // Node mapping table - $nodes = array( - // $method $node $element $styles $data $argument1 $argument2 - 'p' => array('Paragraph', $node, $element, $styles, null, null, null), - 'h1' => array('Heading', null, $element, $styles, null, 'Heading1', null), - 'h2' => array('Heading', null, $element, $styles, null, 'Heading2', null), - 'h3' => array('Heading', null, $element, $styles, null, 'Heading3', null), - 'h4' => array('Heading', null, $element, $styles, null, 'Heading4', null), - 'h5' => array('Heading', null, $element, $styles, null, 'Heading5', null), - 'h6' => array('Heading', null, $element, $styles, null, 'Heading6', null), - '#text' => array('Text', $node, $element, $styles, null, null, null), - 'strong' => array('Property', null, null, $styles, null, 'bold', true), - 'em' => array('Property', null, null, $styles, null, 'italic', true), - 'sup' => array('Property', null, null, $styles, null, 'superScript', true), - 'sub' => array('Property', null, null, $styles, null, 'subScript', true), - 'table' => array('Table', $node, $element, $styles, null, 'addTable', true), - 'tr' => array('Table', $node, $element, $styles, null, 'addRow', true), - 'td' => array('Table', $node, $element, $styles, null, 'addCell', true), - 'ul' => array('List', null, null, $styles, $data, 3, null), - 'ol' => array('List', null, null, $styles, $data, 7, null), - 'li' => array('ListItem', $node, $element, $styles, $data, null, null), - ); + $nodes = [ + // $method $node $element $styles $data $argument1 $argument2 + 'p' => ['Paragraph', $node, $element, $styles, null, null, null], + 'h1' => ['Heading', $node, $element, $styles, null, 'Heading1', null], + 'h2' => ['Heading', $node, $element, $styles, null, 'Heading2', null], + 'h3' => ['Heading', $node, $element, $styles, null, 'Heading3', null], + 'h4' => ['Heading', $node, $element, $styles, null, 'Heading4', null], + 'h5' => ['Heading', $node, $element, $styles, null, 'Heading5', null], + 'h6' => ['Heading', $node, $element, $styles, null, 'Heading6', null], + '#text' => ['Text', $node, $element, $styles, null, null, null], + 'strong' => ['Property', null, null, $styles, null, 'bold', true], + 'b' => ['Property', null, null, $styles, null, 'bold', true], + 'em' => ['Property', null, null, $styles, null, 'italic', true], + 'i' => ['Property', null, null, $styles, null, 'italic', true], + 'u' => ['Property', null, null, $styles, null, 'underline', 'single'], + 'sup' => ['Property', null, null, $styles, null, 'superScript', true], + 'sub' => ['Property', null, null, $styles, null, 'subScript', true], + 'span' => ['Span', $node, null, $styles, null, null, null], + 'font' => ['Span', $node, null, $styles, null, null, null], + 'table' => ['Table', $node, $element, $styles, null, null, null], + 'tr' => ['Row', $node, $element, $styles, null, null, null], + 'td' => ['Cell', $node, $element, $styles, null, null, null], + 'th' => ['Cell', $node, $element, $styles, null, null, null], + 'ul' => ['List', $node, $element, $styles, $data, null, null], + 'ol' => ['List', $node, $element, $styles, $data, null, null], + 'li' => ['ListItem', $node, $element, $styles, $data, null, null], + 'img' => ['Image', $node, $element, $styles, null, null, null], + 'br' => ['LineBreak', null, $element, $styles, null, null, null], + 'a' => ['Link', $node, $element, $styles, null, null, null], + 'input' => ['Input', $node, $element, $styles, null, null, null], + 'hr' => ['HorizRule', $node, $element, $styles, null, null, null], + 'ruby' => ['Ruby', $node, $element, $styles, null, null, null], + ]; $newElement = null; - $keys = array('node', 'element', 'styles', 'data', 'argument1', 'argument2'); + $keys = ['node', 'element', 'styles', 'data', 'argument1', 'argument2']; if (isset($nodes[$node->nodeName])) { // Execute method based on node mapping table and return $newElement or null // Arguments are passed by reference - $arguments = array(); - $args = array(); - list($method, $args[0], $args[1], $args[2], $args[3], $args[4], $args[5]) = $nodes[$node->nodeName]; - for ($i = 0; $i <= 5; $i++) { + $arguments = []; + $args = []; + [$method, $args[0], $args[1], $args[2], $args[3], $args[4], $args[5]] = $nodes[$node->nodeName]; + for ($i = 0; $i <= 5; ++$i) { if ($args[$i] !== null) { $arguments[$keys[$i]] = &$args[$i]; } } $method = "parse{$method}"; - $newElement = call_user_func_array(array('PhpOffice\PhpWord\Shared\Html', $method), $arguments); + $newElement = call_user_func_array(['PhpOffice\PhpWord\Shared\Html', $method], array_values($arguments)); // Retrieve back variables from arguments foreach ($keys as $key) { @@ -159,25 +272,24 @@ protected static function parseNode($node, $element, $styles = array(), $data = $newElement = $element; } - self::parseChildNodes($node, $newElement, $styles, $data); + static::parseChildNodes($node, $newElement, $styles, $data); } /** * Parse child nodes. * - * @param \DOMNode $node - * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param DOMNode $node + * @param AbstractContainer|Row|Table $element * @param array $styles * @param array $data - * @return void */ - private static function parseChildNodes($node, $element, $styles, $data) + protected static function parseChildNodes($node, $element, $styles, $data): void { - if ($node->nodeName != 'li') { + if ('li' != $node->nodeName) { $cNodes = $node->childNodes; - if (count($cNodes) > 0) { + if (!empty($cNodes)) { foreach ($cNodes as $cNode) { - if ($element instanceof AbstractContainer) { + if ($element instanceof AbstractContainer || $element instanceof Table || $element instanceof Row) { self::parseNode($cNode, $element, $styles, $data); } } @@ -186,192 +298,1033 @@ private static function parseChildNodes($node, $element, $styles, $data) } /** - * Parse paragraph node + * Parse paragraph node. * - * @param \DOMNode $node - * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param DOMNode $node + * @param AbstractContainer $element * @param array &$styles - * @return \PhpOffice\PhpWord\Element\TextRun + * + * @return \PhpOffice\PhpWord\Element\PageBreak|TextRun */ - private static function parseParagraph($node, $element, &$styles) + protected static function parseParagraph($node, $element, &$styles) { - $styles['paragraph'] = self::parseInlineStyle($node, $styles['paragraph']); - $newElement = $element->addTextRun($styles['paragraph']); + $styles['paragraph'] = self::recursiveParseStylesInHierarchy($node, $styles['paragraph']); + if (isset($styles['paragraph']['isPageBreak']) && $styles['paragraph']['isPageBreak']) { + return $element->addPageBreak(); + } - return $newElement; + return $element->addTextRun($styles['paragraph']); } /** - * Parse heading node + * Parse input node. * - * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param DOMNode $node + * @param AbstractContainer $element * @param array &$styles + */ + protected static function parseInput($node, $element, &$styles): void + { + $attributes = $node->attributes; + if (null === $attributes->getNamedItem('type')) { + return; + } + + $inputType = $attributes->getNamedItem('type')->nodeValue; + switch ($inputType) { + case 'checkbox': + $checked = ($checked = $attributes->getNamedItem('checked')) && $checked->nodeValue === 'true' ? true : false; + $textrun = $element->addTextRun($styles['paragraph']); + $textrun->addFormField('checkbox')->setValue($checked); + + break; + } + } + + /** + * Parse heading node. + * * @param string $argument1 Name of heading style - * @return \PhpOffice\PhpWord\Element\TextRun * * @todo Think of a clever way of defining header styles, now it is only based on the assumption, that * Heading1 - Heading6 are already defined somewhere */ - private static function parseHeading($element, &$styles, $argument1) + protected static function parseHeading(DOMNode $node, AbstractContainer $element, array &$styles, string $argument1): TextRun { - $styles['paragraph'] = $argument1; - $newElement = $element->addTextRun($styles['paragraph']); + $style = new Paragraph(); + $style->setStyleName($argument1); + $style->setStyleByArray(self::parseInlineStyle($node, $styles['paragraph'])); - return $newElement; + return $element->addTextRun($style); } /** - * Parse text node + * Parse text node. * - * @param \DOMNode $node - * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param DOMNode $node + * @param AbstractContainer $element * @param array &$styles - * @return null */ - private static function parseText($node, $element, &$styles) + protected static function parseText($node, $element, &$styles): void { - $styles['font'] = self::parseInlineStyle($node, $styles['font']); + $styles['font'] = self::recursiveParseStylesInHierarchy($node, $styles['font']); - // Commented as source of bug #257. `method_exists` doesn't seems to work properly in this case. - // @todo Find better error checking for this one - // if (method_exists($element, 'addText')) { - $element->addText($node->nodeValue, $styles['font'], $styles['paragraph']); - // } + //alignment applies on paragraph, not on font. Let's copy it there + if (isset($styles['font']['alignment']) && is_array($styles['paragraph'])) { + $styles['paragraph']['alignment'] = $styles['font']['alignment']; + } - return null; + if (is_callable([$element, 'addText'])) { + $element->addText($node->nodeValue, $styles['font'], $styles['paragraph']); + } } /** - * Parse property node + * Parse property node. * * @param array &$styles * @param string $argument1 Style name * @param string $argument2 Style value - * @return null */ - private static function parseProperty(&$styles, $argument1, $argument2) + protected static function parseProperty(&$styles, $argument1, $argument2): void { $styles['font'][$argument1] = $argument2; + } - return null; + /** + * Parse span node. + * + * @param DOMNode $node + * @param array &$styles + */ + protected static function parseSpan($node, &$styles): void + { + self::parseInlineStyle($node, $styles['font']); } /** - * Parse table node + * Parse table node. * - * @param \DOMNode $node - * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param DOMNode $node + * @param AbstractContainer $element * @param array &$styles - * @param string $argument1 Method name - * @return \PhpOffice\PhpWord\Element\AbstractContainer $element + * + * @return Table $element * * @todo As soon as TableItem, RowItem and CellItem support relative width and height */ - private static function parseTable($node, $element, &$styles, $argument1) + protected static function parseTable($node, $element, &$styles) { - $styles['paragraph'] = self::parseInlineStyle($node, $styles['paragraph']); + $elementStyles = self::parseInlineStyle($node, $styles['table']); - $newElement = $element->$argument1(); + $newElement = $element->addTable($elementStyles); - // $attributes = $node->attributes; - // if ($attributes->getNamedItem('width') !== null) { - // $newElement->setWidth($attributes->getNamedItem('width')->value); - // } + // Add style name from CSS Class + if (isset($elementStyles['className'])) { + $newElement->getStyle()->setStyleName($elementStyles['className']); + } - // if ($attributes->getNamedItem('height') !== null) { - // $newElement->setHeight($attributes->getNamedItem('height')->value); - // } - // if ($attributes->getNamedItem('width') !== null) { - // $newElement=$element->addCell($width=$attributes->getNamedItem('width')->value); - // } + $attributes = $node->attributes; + if ($attributes->getNamedItem('border')) { + $border = (int) $attributes->getNamedItem('border')->nodeValue; + $newElement->getStyle()->setBorderSize(Converter::pixelToTwip($border)); + } return $newElement; } /** - * Parse list node + * Parse a table row. + * + * @param DOMNode $node + * @param Table $element + * @param array &$styles + * + * @return Row $element + */ + protected static function parseRow($node, $element, &$styles) + { + $rowStyles = self::parseInlineStyle($node, $styles['row']); + if ($node->parentNode->nodeName == 'thead') { + $rowStyles['tblHeader'] = true; + } + + // set cell height to control row heights + $height = $rowStyles['height'] ?? null; + unset($rowStyles['height']); // would not apply + + return $element->addRow($height, $rowStyles); + } + + /** + * Parse table cell. + * + * @param DOMNode $node + * @param Table $element + * @param array &$styles + * + * @return \PhpOffice\PhpWord\Element\Cell|TextRun $element + */ + protected static function parseCell($node, $element, &$styles) + { + $cellStyles = self::recursiveParseStylesInHierarchy($node, $styles['cell']); + + $colspan = $node->getAttribute('colspan'); + if (!empty($colspan)) { + $cellStyles['gridSpan'] = $colspan - 0; + } + + // set cell width to control column widths + $width = $cellStyles['width'] ?? null; + unset($cellStyles['width']); // would not apply + $cell = $element->addCell($width, $cellStyles); + + if (self::shouldAddTextRun($node)) { + return $cell->addTextRun(self::filterOutNonInheritedStyles(self::parseInlineStyle($node, $styles['paragraph']))); + } + + return $cell; + } + + /** + * Checks if $node contains an HTML element that cannot be added to TextRun. + * + * @return bool Returns true if the node contains an HTML element that cannot be added to TextRun + */ + protected static function shouldAddTextRun(DOMNode $node) + { + $containsBlockElement = self::$xpath->query('.//table|./p|./ul|./ol|./h1|./h2|./h3|./h4|./h5|./h6', $node)->length > 0; + if ($containsBlockElement) { + return false; + } + + return true; + } + + /** + * Recursively parses styles on parent nodes + * TODO if too slow, add caching of parent nodes, !! everything is static here so watch out for concurrency !! + */ + protected static function recursiveParseStylesInHierarchy(DOMNode $node, array $style) + { + $parentStyle = []; + if ($node->parentNode != null && XML_ELEMENT_NODE == $node->parentNode->nodeType) { + $parentStyle = self::recursiveParseStylesInHierarchy($node->parentNode, []); + } + if ($node->nodeName === '#text') { + $parentStyle = array_merge($parentStyle, $style); + } else { + $parentStyle = self::filterOutNonInheritedStyles($parentStyle); + } + $style = self::parseInlineStyle($node, $parentStyle); + + return $style; + } + + /** + * Removes non-inherited styles from array. + */ + protected static function filterOutNonInheritedStyles(array $styles) + { + $nonInheritedStyles = [ + 'borderSize', + 'borderTopSize', + 'borderRightSize', + 'borderBottomSize', + 'borderLeftSize', + 'borderColor', + 'borderTopColor', + 'borderRightColor', + 'borderBottomColor', + 'borderLeftColor', + 'borderStyle', + 'spaceAfter', + 'spaceBefore', + 'underline', + 'strikethrough', + 'hidden', + ]; + + $styles = array_diff_key($styles, array_flip($nonInheritedStyles)); + + return $styles; + } + + /** + * Parse list node. * + * @param DOMNode $node + * @param AbstractContainer $element * @param array &$styles * @param array &$data - * @param string $argument1 List type - * @return null */ - private static function parseList(&$styles, &$data, $argument1) + protected static function parseList($node, $element, &$styles, &$data) { + $isOrderedList = $node->nodeName === 'ol'; if (isset($data['listdepth'])) { - $data['listdepth']++; + ++$data['listdepth']; } else { $data['listdepth'] = 0; + $styles['list'] = 'listStyle_' . self::$listIndex++; + $style = $element->getPhpWord()->addNumberingStyle($styles['list'], self::getListStyle($isOrderedList)); + + // extract attributes start & type e.g.
      + $start = 0; + $type = ''; + foreach ($node->attributes as $attribute) { + switch ($attribute->name) { + case 'start': + $start = (int) $attribute->value; + + break; + case 'type': + $type = $attribute->value; + + break; + } + } + + $levels = $style->getLevels(); + /** @var \PhpOffice\PhpWord\Style\NumberingLevel */ + $level = $levels[0]; + if ($start > 0) { + $level->setStart($start); + } + $type = $type ? self::mapListType($type) : null; + if ($type) { + $level->setFormat($type); + } } - $styles['list']['listType'] = $argument1; + if ($node->parentNode->nodeName === 'li') { + return $element->getParent(); + } + } - return null; + /** + * @param bool $isOrderedList + * + * @return array + */ + protected static function getListStyle($isOrderedList) + { + if ($isOrderedList) { + return [ + 'type' => 'multilevel', + 'levels' => [ + ['format' => NumberFormat::DECIMAL, 'text' => '%1.', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360], + ['format' => NumberFormat::LOWER_LETTER, 'text' => '%2.', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360], + ['format' => NumberFormat::LOWER_ROMAN, 'text' => '%3.', 'alignment' => 'right', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 180], + ['format' => NumberFormat::DECIMAL, 'text' => '%4.', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360], + ['format' => NumberFormat::LOWER_LETTER, 'text' => '%5.', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360], + ['format' => NumberFormat::LOWER_ROMAN, 'text' => '%6.', 'alignment' => 'right', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 180], + ['format' => NumberFormat::DECIMAL, 'text' => '%7.', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360], + ['format' => NumberFormat::LOWER_LETTER, 'text' => '%8.', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360], + ['format' => NumberFormat::LOWER_ROMAN, 'text' => '%9.', 'alignment' => 'right', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 180], + ], + ]; + } + + return [ + 'type' => 'hybridMultilevel', + 'levels' => [ + ['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'], + ], + ]; } /** - * Parse list item node + * Parse list item node. * - * @param \DOMNode $node - * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param DOMNode $node + * @param AbstractContainer $element * @param array &$styles * @param array $data - * @return null * * @todo This function is almost the same like `parseChildNodes`. Merged? * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete parsing part of childNodes */ - private static function parseListItem($node, $element, &$styles, $data) + protected static function parseListItem($node, $element, &$styles, $data): void { $cNodes = $node->childNodes; - if (count($cNodes) > 0) { - $text = ''; + if (!empty($cNodes)) { + $listRun = $element->addListItemRun($data['listdepth'], $styles['list'], $styles['paragraph']); foreach ($cNodes as $cNode) { - if ($cNode->nodeName == '#text') { - $text = $cNode->nodeValue; - } + self::parseNode($cNode, $listRun, $styles, $data); } - $element->addListItem($text, $data['listdepth'], $styles['font'], $styles['list'], $styles['paragraph']); } - - return null; } /** - * Parse style + * Parse style. * - * @param \DOMAttr $attribute - * @param array $styles - * @return array + * @param DOMNode $attribute */ - private static function parseStyle($attribute, $styles) + protected static function parseStyle($attribute, array $styles): array { - $properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;")); + $properties = explode(';', trim($attribute->nodeValue, " \t\n\r\0\x0B;")); + + $selectors = []; foreach ($properties as $property) { - list($cKey, $cValue) = explode(':', $property, 2); - $cValue = trim($cValue); - switch (trim($cKey)) { + [$cKey, $cValue] = array_pad(explode(':', $property, 2), 2, null); + $selectors[strtolower(trim($cKey))] = trim($cValue ?? ''); + } + + return self::parseStyleDeclarations($selectors, $styles); + } + + protected static function parseStyleDeclarations(array $selectors, array $styles): array + { + $bidi = ($selectors['direction'] ?? '') === 'rtl'; + foreach ($selectors as $property => $value) { + switch ($property) { case 'text-decoration': - switch ($cValue) { + switch ($value) { case 'underline': $styles['underline'] = 'single'; + break; case 'line-through': $styles['strikethrough'] = true; + break; } + break; case 'text-align': - $styles['align'] = $cValue; + $styles['alignment'] = self::mapAlign($value, $bidi); + + break; + case 'ruby-align': + $styles['rubyAlignment'] = self::mapRubyAlign($value); + + break; + case 'display': + $styles['hidden'] = $value === 'none' || $value === 'hidden'; + + break; + case 'direction': + $styles['rtl'] = $value === 'rtl'; + $styles['bidi'] = $value === 'rtl'; + + break; + case 'font-size': + $styles['size'] = Converter::cssToPoint($value); + + break; + case 'font-family': + $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 ($value === 'normal' || $value === 'inherit') { + $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO; + $spacing = 0; + } 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]+)%/', $value, $matches)) { + //matches percentages + $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO; + //we are subtracting 1 line height because the Spacing writer is adding one line + $spacing = ((((int) $matches[1]) / 100) * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT; + } else { + //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 = ($value * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT; + } + $styles['spacingLineRule'] = $spacingLineRule; + $styles['line-spacing'] = $spacing; + + break; + case 'letter-spacing': + $styles['letter-spacing'] = Converter::cssToTwip($value); + + break; + case 'text-indent': + $styles['indentation']['firstLine'] = Converter::cssToTwip($value); + + break; + case 'font-weight': + $tValue = false; + if (preg_match('#bold#', $value)) { + $tValue = true; // also match bolder + } + $styles['bold'] = $tValue; + + break; + case 'font-style': + $tValue = false; + 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': + $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($value) + $styles['spaceBefore'] = Converter::cssToTwip($value); + + break; + case 'margin-bottom': + // BC change: up to ver. 0.17.0 incorrectly converted to points - Converter::cssToPoint($value) + $styles['spaceAfter'] = Converter::cssToTwip($value); + + break; + + case 'padding': + $valueTop = $valueRight = $valueBottom = $valueLeft = null; + $cValue = preg_replace('# +#', ' ', trim($value)); + $paddingArr = explode(' ', $cValue); + $countParams = count($paddingArr); + if ($countParams == 1) { + $valueTop = $valueRight = $valueBottom = $valueLeft = $paddingArr[0]; + } elseif ($countParams == 2) { + $valueTop = $valueBottom = $paddingArr[0]; + $valueRight = $valueLeft = $paddingArr[1]; + } elseif ($countParams == 3) { + $valueTop = $paddingArr[0]; + $valueRight = $valueLeft = $paddingArr[1]; + $valueBottom = $paddingArr[2]; + } elseif ($countParams == 4) { + $valueTop = $paddingArr[0]; + $valueRight = $paddingArr[1]; + $valueBottom = $paddingArr[2]; + $valueLeft = $paddingArr[3]; + } + if ($valueTop !== null) { + $styles['paddingTop'] = Converter::cssToTwip($valueTop); + } + if ($valueRight !== null) { + $styles['paddingRight'] = Converter::cssToTwip($valueRight); + } + if ($valueBottom !== null) { + $styles['paddingBottom'] = Converter::cssToTwip($valueBottom); + } + if ($valueLeft !== null) { + $styles['paddingLeft'] = Converter::cssToTwip($valueLeft); + } + + break; + case 'padding-top': + $styles['paddingTop'] = Converter::cssToTwip($value); + + break; + case 'padding-right': + $styles['paddingRight'] = Converter::cssToTwip($value); + + break; + case 'padding-bottom': + $styles['paddingBottom'] = Converter::cssToTwip($value); + + break; + case 'padding-left': + $styles['paddingLeft'] = Converter::cssToTwip($value); + + break; + + case 'border-color': + self::mapBorderColor($styles, $value); + + break; + case 'border-width': + $styles['borderSize'] = Converter::cssToPoint($value); + + break; + case 'border-style': + $styles['borderStyle'] = self::mapBorderStyle($value); + + break; + case 'width': + 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]+)%/', $value, $matches)) { + $styles['width'] = $matches[1] * 50; + $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT; + } 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': + case 'border-bottom': + case 'border-right': + case 'border-left': + // must have exact order [width color style], e.g. "1px #0011CC solid" or "2pt green solid" + // Word does not accept shortened hex colors e.g. #CCC, only full e.g. #CCCCCC + if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+|[a-zA-Z]+)\s+([a-z]+)/', $value, $matches)) { + if (false !== strpos($property, '-')) { + $tmp = explode('-', $property); + $which = $tmp[1]; + $which = ucfirst($which); // e.g. bottom -> Bottom + } else { + $which = ''; + } + // Note - border width normalization: + // Width of border in Word is calculated differently than HTML borders, usually showing up too bold. + // Smallest 1px (or 1pt) appears in Word like 2-3px/pt in HTML once converted to twips. + // Therefore we need to normalize converted twip value to cca 1/2 of value. + // This may be adjusted, if better ratio or formula found. + // BC change: up to ver. 0.17.0 was $size converted to points - Converter::cssToPoint($size) + $size = Converter::cssToTwip($matches[1]); + $size = (int) ($size / 2); + // valid variants may be e.g. borderSize, borderTopSize, borderLeftColor, etc .. + $styles["border{$which}Size"] = $size; // twips + $styles["border{$which}Color"] = trim($matches[2], '#'); + $styles["border{$which}Style"] = self::mapBorderStyle($matches[3]); + } + + break; + case 'vertical-align': + // https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align + if (preg_match('#(?:top|bottom|middle|sub|baseline)#i', $value, $matches)) { + $styles['valign'] = self::mapAlignVertical($matches[0]); + } + + break; + case 'page-break-after': + if ($value == 'always') { + $styles['isPageBreak'] = true; + } + break; } } return $styles; } + + /** + * Parse image node. + * + * @param DOMNode $node + * @param AbstractContainer $element + * + * @return \PhpOffice\PhpWord\Element\Image + */ + protected static function parseImage($node, $element) + { + $style = []; + $src = null; + foreach ($node->attributes as $attribute) { + switch ($attribute->name) { + case 'src': + $src = $attribute->value; + + break; + case 'width': + $style['width'] = self::convertHtmlSize($attribute->value); + $style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX; + + break; + case 'height': + $style['height'] = self::convertHtmlSize($attribute->value); + $style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX; + + break; + case 'style': + $styleattr = explode(';', $attribute->value); + foreach ($styleattr as $attr) { + if (strpos($attr, ':')) { + [$k, $v] = explode(':', $attr); + switch ($k) { + case 'float': + if (trim($v) == 'right') { + $style['hPos'] = \PhpOffice\PhpWord\Style\Image::POS_RIGHT; + $style['hPosRelTo'] = \PhpOffice\PhpWord\Style\Image::POS_RELTO_MARGIN; // inner section area + $style['pos'] = \PhpOffice\PhpWord\Style\Image::POS_RELATIVE; + $style['wrap'] = \PhpOffice\PhpWord\Style\Image::WRAP_TIGHT; + $style['overlap'] = true; + } + if (trim($v) == 'left') { + $style['hPos'] = \PhpOffice\PhpWord\Style\Image::POS_LEFT; + $style['hPosRelTo'] = \PhpOffice\PhpWord\Style\Image::POS_RELTO_MARGIN; // inner section area + $style['pos'] = \PhpOffice\PhpWord\Style\Image::POS_RELATIVE; + $style['wrap'] = \PhpOffice\PhpWord\Style\Image::WRAP_TIGHT; + $style['overlap'] = true; + } + + break; + } + } + } + + break; + } + } + $originSrc = $src; + if (strpos($src, 'data:image') !== false) { + $tmpDir = Settings::getTempDir() . '/'; + + $match = []; + preg_match('/data:image\/(\w+);base64,(.+)/', $src, $match); + if (!empty($match)) { + $src = $imgFile = $tmpDir . uniqid() . '.' . $match[1]; + + $ifp = fopen($imgFile, 'wb'); + + if ($ifp !== false) { + fwrite($ifp, base64_decode($match[2])); + fclose($ifp); + } + } + } + $src = urldecode($src); + + if (!is_file($src) + && null !== self::$options + && isset(self::$options['IMG_SRC_SEARCH'], self::$options['IMG_SRC_REPLACE']) + ) { + $src = str_replace(self::$options['IMG_SRC_SEARCH'], self::$options['IMG_SRC_REPLACE'], $src); + } + + if (!is_file($src)) { + if ($imgBlob = @file_get_contents($src)) { + $tmpDir = Settings::getTempDir() . '/'; + $match = []; + preg_match('/.+\.(\w+)$/', $src, $match); + $src = $tmpDir . uniqid(); + if (isset($match[1])) { + $src .= '.' . $match[1]; + } + + $ifp = fopen($src, 'wb'); + + if ($ifp !== false) { + fwrite($ifp, $imgBlob); + fclose($ifp); + } + } + } + + if (is_file($src)) { + $newElement = $element->addImage($src, $style); + } else { + throw new Exception("Could not load image $originSrc"); + } + + return $newElement; + } + + /** + * Transforms a CSS border style into a word border style. + * + * @param string $cssBorderStyle + * + * @return null|string + */ + protected static function mapBorderStyle($cssBorderStyle) + { + switch ($cssBorderStyle) { + case 'none': + case 'dashed': + case 'dotted': + case 'double': + return $cssBorderStyle; + default: + return 'single'; + } + } + + protected static function mapBorderColor(&$styles, $cssBorderColor): void + { + $numColors = substr_count($cssBorderColor, '#'); + if ($numColors === 1) { + $styles['borderColor'] = trim($cssBorderColor, '#'); + } elseif ($numColors > 1) { + $colors = explode(' ', $cssBorderColor); + $borders = ['borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor']; + for ($i = 0; $i < min(4, $numColors, count($colors)); ++$i) { + $styles[$borders[$i]] = trim($colors[$i], '#'); + } + } + } + + /** + * Transforms a HTML/CSS alignment into a \PhpOffice\PhpWord\SimpleType\Jc. + * + * @param string $cssAlignment + * @param bool $bidi + * + * @return null|string + */ + protected static function mapAlign($cssAlignment, $bidi) + { + switch ($cssAlignment) { + case 'right': + return $bidi ? Jc::START : Jc::END; + case 'center': + return Jc::CENTER; + case 'justify': + return Jc::BOTH; + default: + return $bidi ? Jc::END : Jc::START; + } + } + + /** + * Transforms a HTML/CSS ruby alignment into a \PhpOffice\PhpWord\SimpleType\Jc. + */ + protected static function mapRubyAlign(string $cssRubyAlignment): string + { + switch ($cssRubyAlignment) { + case 'center': + return RubyProperties::ALIGNMENT_CENTER; + case 'start': + return RubyProperties::ALIGNMENT_LEFT; + case 'space-between': + return RubyProperties::ALIGNMENT_DISTRIBUTE_SPACE; + default: + return ''; + } + } + + /** + * Transforms a HTML/CSS vertical alignment. + * + * @param string $alignment + * + * @return null|string + */ + protected static function mapAlignVertical($alignment) + { + $alignment = strtolower($alignment); + switch ($alignment) { + case 'top': + case 'baseline': + case 'bottom': + return $alignment; + case 'middle': + return 'center'; + case 'sub': + return 'bottom'; + case 'text-top': + case 'baseline': + return 'top'; + default: + // @discuss - which one should apply: + // - Word uses default vert. alignment: top + // - all browsers use default vert. alignment: middle + // Returning empty string means attribute wont be set so use Word default (top). + return ''; + } + } + + /** + * Map list style for ordered list. + * + * @param string $cssListType + */ + protected static function mapListType($cssListType) + { + switch ($cssListType) { + case 'a': + return NumberFormat::LOWER_LETTER; // a, b, c, .. + case 'A': + return NumberFormat::UPPER_LETTER; // A, B, C, .. + case 'i': + return NumberFormat::LOWER_ROMAN; // i, ii, iii, iv, .. + case 'I': + return NumberFormat::UPPER_ROMAN; // I, II, III, IV, .. + case '1': + default: + return NumberFormat::DECIMAL; // 1, 2, 3, .. + } + } + + /** + * Parse line break. + * + * @param AbstractContainer $element + */ + protected static function parseLineBreak($element): void + { + $element->addTextBreak(); + } + + /** + * Parse link node. + * + * @param DOMNode $node + * @param AbstractContainer $element + * @param array $styles + */ + protected static function parseLink($node, $element, &$styles) + { + $target = null; + foreach ($node->attributes as $attribute) { + switch ($attribute->name) { + case 'href': + $target = $attribute->value; + + break; + } + } + $styles['font'] = self::parseInlineStyle($node, $styles['font']); + + if (empty($target)) { + $target = '#'; + } + + if (strpos($target, '#') === 0 && strlen($target) > 1) { + return $element->addLink(substr($target, 1), $node->textContent, $styles['font'], $styles['paragraph'], true); + } + + return $element->addLink($target, $node->textContent, $styles['font'], $styles['paragraph']); + } + + /** + * Render horizontal rule + * Note: Word rule is not the same as HTML's
      since it does not support width and thus neither alignment. + * + * @param DOMNode $node + * @param AbstractContainer $element + */ + protected static function parseHorizRule($node, $element): void + { + $styles = self::parseInlineStyle($node); + + //
      is implemented as an empty paragraph - extending 100% inside the section + // Some properties may be controlled, e.g.
      + + $fontStyle = $styles + ['size' => 3]; + + $paragraphStyle = $styles + [ + 'lineHeight' => 0.25, // multiply default line height - e.g. 1, 1.5 etc + 'spacing' => 0, // twip + 'spaceBefore' => 120, // twip, 240/2 (default line height) + 'spaceAfter' => 120, // twip + 'borderBottomSize' => empty($styles['line-height']) ? 1 : $styles['line-height'], + 'borderBottomColor' => empty($styles['color']) ? '000000' : $styles['color'], + 'borderBottomStyle' => 'single', // same as "solid" + ]; + + $element->addText('', $fontStyle, $paragraphStyle); + + // Notes:
      cannot be: + // - table - throws error "cannot be inside textruns", e.g. lists + // - line - that is a shape, has different behaviour + // - repeated text, e.g. underline "_", because of unpredictable line wrapping + } + + /** + * Parse ruby node. + * + * @param DOMNode $node + * @param AbstractContainer $element + * @param array $styles + */ + protected static function parseRuby($node, $element, &$styles) + { + $rubyProperties = new RubyProperties(); + $baseTextRun = new TextRun($styles['paragraph']); + $rubyTextRun = new TextRun(null); + if ($node->hasAttributes()) { + $langAttr = $node->attributes->getNamedItem('lang'); + if ($langAttr !== null) { + $rubyProperties->setLanguageId($langAttr->textContent); + } + $styleAttr = $node->attributes->getNamedItem('style'); + if ($styleAttr !== null) { + $styles = self::parseStyle($styleAttr, $styles['paragraph']); + if (isset($styles['rubyAlignment']) && $styles['rubyAlignment'] !== '') { + $rubyProperties->setAlignment($styles['rubyAlignment']); + } + if (isset($styles['size']) && $styles['size'] !== '') { + $rubyProperties->setFontSizeForBaseText($styles['size']); + } + $baseTextRun->setParagraphStyle($styles); + } + } + foreach ($node->childNodes as $child) { + if ($child->nodeName === '#text') { + $content = trim($child->textContent); + if ($content !== '') { + $baseTextRun->addText($content); + } + } elseif ($child->nodeName === 'rt') { + $rubyTextRun->addText(trim($child->textContent)); + if ($child->hasAttributes()) { + $styleAttr = $child->attributes->getNamedItem('style'); + if ($styleAttr !== null) { + $styles = self::parseStyle($styleAttr, []); + if (isset($styles['size']) && $styles['size'] !== '') { + $rubyProperties->setFontFaceSize($styles['size']); + } + $rubyTextRun->setParagraphStyle($styles); + } + } + } + } + + return $element->addRuby($baseTextRun, $rubyTextRun, $rubyProperties); + } + + 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, '# '); + } + + /** + * Transform HTML sizes (pt, px) in pixels. + */ + protected static function convertHtmlSize(string $size): float + { + // pt + if (false !== strpos($size, 'pt')) { + return Converter::pointToPixel((float) str_replace('pt', '', $size)); + } + + // px + if (false !== strpos($size, 'px')) { + return (float) str_replace('px', '', $size); + } + + return (float) $size; + } } diff --git a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php new file mode 100644 index 0000000000..4762cc7104 --- /dev/null +++ b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php @@ -0,0 +1,252 @@ + 4) ? 0xFFFFFFFF : -1; + private const HIGH_ORDER_BIT = (PHP_INT_SIZE > 4) ? 0x80000000 : PHP_INT_MIN; + + /** + * Mapping between algorithm name and algorithm ID. + * + * @var array + * + * @see https://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing.writeprotection.cryptographicalgorithmsid(v=office.14).aspx + */ + private static $algorithmMapping = [ + self::ALGORITHM_MD2 => [1, 'md2'], + self::ALGORITHM_MD4 => [2, 'md4'], + self::ALGORITHM_MD5 => [3, 'md5'], + self::ALGORITHM_SHA_1 => [4, 'sha1'], + self::ALGORITHM_MAC => [5, ''], // 'mac' -> not possible with hash() + self::ALGORITHM_RIPEMD => [6, 'ripemd'], + self::ALGORITHM_RIPEMD_160 => [7, 'ripemd160'], + self::ALGORITHM_HMAC => [9, ''], //'hmac' -> not possible with hash() + self::ALGORITHM_SHA_256 => [12, 'sha256'], + self::ALGORITHM_SHA_384 => [13, 'sha384'], + self::ALGORITHM_SHA_512 => [14, 'sha512'], + ]; + + private static $initialCodeArray = [ + 0xE1F0, + 0x1D0F, + 0xCC9C, + 0x84C0, + 0x110C, + 0x0E10, + 0xF1CE, + 0x313E, + 0x1872, + 0xE139, + 0xD40F, + 0x84F9, + 0x280C, + 0xA96A, + 0x4EC3, + ]; + + private static $encryptionMatrix = [ + [0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09], + [0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF], + [0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0], + [0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40], + [0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5], + [0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A], + [0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9], + [0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0], + [0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC], + [0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10], + [0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168], + [0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C], + [0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD], + [0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC], + [0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4], + ]; + + private static $passwordMaxLength = 15; + + /** + * Create a hashed password that MS Word will be able to work with. + * + * @see https://blogs.msdn.microsoft.com/vsod/2010/04/05/how-to-set-the-editing-restrictions-in-word-using-open-xml-sdk-2-0/ + * + * @param string $password + * @param string $algorithmName + * @param string $salt + * @param int $spinCount + * + * @return string + */ + public static function hashPassword($password, $algorithmName = self::ALGORITHM_SHA_1, $salt = null, $spinCount = 10000) + { + $origEncoding = mb_internal_encoding(); + mb_internal_encoding('UTF-8'); + + $password = mb_substr($password, 0, min(self::$passwordMaxLength, mb_strlen($password))); + + // Get the single-byte values by iterating through the Unicode characters of the truncated password. + // For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte. + $passUtf8 = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'); + if (!is_string($passUtf8)) { + throw new Exception('Failed to convert password to UCS-2LE'); + } + + $byteChars = []; + for ($i = 0; $i < mb_strlen($password); ++$i) { + $byteChars[$i] = ord(substr($passUtf8, $i * 2, 1)); + + if ($byteChars[$i] == 0) { + $byteChars[$i] = ord(substr($passUtf8, $i * 2 + 1, 1)); + } + } + + // 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 & 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'); + + // Implementation Notes List: + // Word requires that the initial hash of the password with the salt not be considered in the count. + // The initial hash of salt + key is not included in the iteration count. + $algorithm = self::getAlgorithm($algorithmName); + $generatedKey = hash($algorithm, $salt . $generatedKey, true); + + for ($i = 0; $i < $spinCount; ++$i) { + $generatedKey = hash($algorithm, $generatedKey . pack('CCCC', $i, $i >> 8, $i >> 16, $i >> 24), true); + } + $generatedKey = base64_encode($generatedKey); + + mb_internal_encoding($origEncoding); + + return $generatedKey; + } + + /** + * Get algorithm from self::$algorithmMapping. + * + * @param string $algorithmName + * + * @return string + */ + private static function getAlgorithm($algorithmName) + { + $algorithm = self::$algorithmMapping[$algorithmName][1]; + if ($algorithm == '') { + $algorithm = 'sha1'; + } + + return $algorithm; + } + + /** + * Returns the algorithm ID. + * + * @param string $algorithmName + * + * @return int + */ + public static function getAlgorithmId($algorithmName) + { + return self::$algorithmMapping[$algorithmName][0]; + } + + /** + * Build combined key from low-order word and high-order word. + * + * @param array $byteChars byte array representation of password + * + * @return int + */ + private static function buildCombinedKey($byteChars) + { + $byteCharsLength = count($byteChars); + // Compute the high-order word + // Initialize from the initial code array (see above), depending on the passwords length. + $highOrderWord = self::$initialCodeArray[$byteCharsLength - 1]; + + // For each character in the password: + // For every bit in the character, starting with the least significant and progressing to (but excluding) + // the most significant, if the bit is set, XOR the key’s high-order word with the corresponding word from + // the Encryption Matrix + for ($i = 0; $i < $byteCharsLength; ++$i) { + $tmp = self::$passwordMaxLength - $byteCharsLength + $i; + $matrixRow = self::$encryptionMatrix[$tmp]; + for ($intBit = 0; $intBit < 7; ++$intBit) { + if (($byteChars[$i] & (0x0001 << $intBit)) != 0) { + $highOrderWord = ($highOrderWord ^ $matrixRow[$intBit]); + } + } + } + + // Compute low-order word + // Initialize with 0 + $lowOrderWord = 0; + // For each character in the password, going backwards + for ($i = $byteCharsLength - 1; $i >= 0; --$i) { + // low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character + $lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteChars[$i]); + } + // Lastly, low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR strPassword length XOR 0xCE4B. + $lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteCharsLength ^ 0xCE4B); + + // Combine the Low and High Order Word + return self::int32(($highOrderWord << 16) + $lowOrderWord); + } + + /** + * Simulate behaviour of (signed) int32. + * + * @codeCoverageIgnore + * + * @param int $value + * + * @return int + */ + private static function int32($value) + { + $value = $value & self::ALL_ONE_BITS; + + 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 82815afc4b..d4399d6fb7 100644 --- a/src/PhpWord/Shared/OLERead.php +++ b/src/PhpWord/Shared/OLERead.php @@ -1,15 +1,22 @@ numBigBlockDepotBlocks; + // @codeCoverageIgnoreStart if ($this->numExtensionBlocks != 0) { - $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS)/4; + $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4; } + // @codeCoverageIgnoreEnd for ($i = 0; $i < $bbdBlocks; ++$i) { $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos); $pos += 4; } + // @codeCoverageIgnoreStart for ($j = 0; $j < $this->numExtensionBlocks; ++$j) { $pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE; $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1); @@ -126,6 +145,7 @@ public function read($sFileName) $this->extensionBlock = self::getInt4d($this->data, $pos); } } + // @codeCoverageIgnoreEnd $pos = 0; $this->bigBlockChain = ''; @@ -133,8 +153,8 @@ public function read($sFileName) for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) { $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE; - $this->bigBlockChain .= substr($this->data, $pos, 4*$bbs); - $pos += 4*$bbs; + $this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs); + $pos += 4 * $bbs; } $pos = 0; @@ -143,10 +163,10 @@ public function read($sFileName) while ($sbdBlock != -2) { $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE; - $this->smallBlockChain .= substr($this->data, $pos, 4*$bbs); - $pos += 4*$bbs; + $this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs); + $pos += 4 * $bbs; - $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock*4); + $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4); } // read the directory stream @@ -159,6 +179,7 @@ public function read($sFileName) /** * Extract binary stream data * + * @param mixed $stream * @return string */ public function getStream($stream) @@ -178,30 +199,30 @@ public function getStream($stream) $pos = $block * self::SMALL_BLOCK_SIZE; $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE); - $block = self::getInt4d($this->smallBlockChain, $block*4); + $block = self::getInt4d($this->smallBlockChain, $block * 4); } return $streamData; - } else { - $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE; - if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) { - ++$numBlocks; - } + } - if ($numBlocks == 0) { - return ''; - } + $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE; + if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) { + ++$numBlocks; + } - $block = $this->props[$stream]['startBlock']; + if ($numBlocks == 0) { + return ''; // @codeCoverageIgnore + } - while ($block != -2) { - $pos = ($block + 1) * self::BIG_BLOCK_SIZE; - $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); - $block = self::getInt4d($this->bigBlockChain, $block*4); - } + $block = $this->props[$stream]['startBlock']; - return $streamData; + while ($block != -2) { + $pos = ($block + 1) * self::BIG_BLOCK_SIZE; + $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); + $block = self::getInt4d($this->bigBlockChain, $block * 4); } + + return $streamData; } /** @@ -218,8 +239,9 @@ private function readData($blSectorId) while ($block != -2) { $pos = ($block + 1) * self::BIG_BLOCK_SIZE; $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); - $block = self::getInt4d($this->bigBlockChain, $block*4); + $block = self::getInt4d($this->bigBlockChain, $block * 4); } + return $data; } @@ -237,7 +259,7 @@ private function readPropertySets() $data = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE); // size in bytes of name - $nameSize = ord($data[self::SIZE_OF_NAME_POS]) | (ord($data[self::SIZE_OF_NAME_POS+1]) << 8); + $nameSize = ord($data[self::SIZE_OF_NAME_POS]) | (ord($data[self::SIZE_OF_NAME_POS + 1]) << 8); // type of entry $type = ord($data[self::TYPE_POS]); @@ -248,14 +270,13 @@ private function readPropertySets() $size = self::getInt4d($data, self::SIZE_POS); - $name = str_replace("\x00", "", substr($data, 0, $nameSize)); + $name = str_replace("\x00", '', substr($data, 0, $nameSize)); - - $this->props[] = array ( - 'name' => $name, - 'type' => $type, + $this->props[] = array( + 'name' => $name, + 'type' => $type, 'startBlock' => $startBlock, - 'size' => $size); + 'size' => $size, ); // tmp helper to simplify checks $upName = strtoupper($name); @@ -286,7 +307,6 @@ private function readPropertySets() $offset += self::PROPERTY_STORAGE_BLOCK_SIZE; } - } /** @@ -308,6 +328,7 @@ private static function getInt4d($data, $pos) } else { $ord24 = ($or24 & 127) << 24; } + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24; } } diff --git a/src/PhpWord/Shared/PCLZip/pclzip.lib.php b/src/PhpWord/Shared/PCLZip/pclzip.lib.php index 4e2a496f20..5243b3c3a0 100644 --- a/src/PhpWord/Shared/PCLZip/pclzip.lib.php +++ b/src/PhpWord/Shared/PCLZip/pclzip.lib.php @@ -25,5473 +25,5185 @@ // $Id: pclzip.lib.php,v 1.60 2009/09/30 21:01:04 vblavet Exp $ // -------------------------------------------------------------------------------- - // ----- Constants - if (!defined('PCLZIP_READ_BLOCK_SIZE')) { - define( 'PCLZIP_READ_BLOCK_SIZE', 2048 ); - } - - // ----- File list separator - // In version 1.x of PclZip, the separator for file list is a space - // (which is not a very smart choice, specifically for windows paths !). - // A better separator should be a comma (,). This constant gives you the - // abilty to change that. - // However notice that changing this value, may have impact on existing - // scripts, using space separated filenames. - // Recommanded values for compatibility with older versions : - //define( 'PCLZIP_SEPARATOR', ' ' ); - // Recommanded values for smart separation of filenames. - if (!defined('PCLZIP_SEPARATOR')) { - define( 'PCLZIP_SEPARATOR', ',' ); - } - - // ----- Error configuration - // 0 : PclZip Class integrated error handling - // 1 : PclError external library error handling. By enabling this - // you must ensure that you have included PclError library. - // [2,...] : reserved for futur use - if (!defined('PCLZIP_ERROR_EXTERNAL')) { - define( 'PCLZIP_ERROR_EXTERNAL', 0 ); - } - - // ----- Optional static temporary directory - // By default temporary files are generated in the script current - // path. - // If defined : - // - MUST BE terminated by a '/'. - // - MUST be a valid, already created directory - // Samples : - // define( 'PCLZIP_TEMPORARY_DIR', '/temp/' ); - // define( 'PCLZIP_TEMPORARY_DIR', 'C:/Temp/' ); - if (!defined('PCLZIP_TEMPORARY_DIR')) { - define( 'PCLZIP_TEMPORARY_DIR', '' ); - } - - // ----- Optional threshold ratio for use of temporary files - // Pclzip sense the size of the file to add/extract and decide to - // use or not temporary file. The algorythm is looking for - // memory_limit of PHP and apply a ratio. - // threshold = memory_limit * ratio. - // Recommended values are under 0.5. Default 0.47. - // Samples : - // define( 'PCLZIP_TEMPORARY_FILE_RATIO', 0.5 ); - if (!defined('PCLZIP_TEMPORARY_FILE_RATIO')) { - define( 'PCLZIP_TEMPORARY_FILE_RATIO', 0.47 ); - } +// ----- Constants +if (!defined('PCLZIP_READ_BLOCK_SIZE')) { + define('PCLZIP_READ_BLOCK_SIZE', 2048); +} + +// ----- File list separator +// In version 1.x of PclZip, the separator for file list is a space +// (which is not a very smart choice, specifically for windows paths !). +// A better separator should be a comma (,). This constant gives you the +// abilty to change that. +// However notice that changing this value, may have impact on existing +// scripts, using space separated filenames. +// Recommanded values for compatibility with older versions : +//define( 'PCLZIP_SEPARATOR', ' ' ); +// Recommanded values for smart separation of filenames. +if (!defined('PCLZIP_SEPARATOR')) { + define('PCLZIP_SEPARATOR', ','); +} + +// ----- Error configuration +// 0 : PclZip Class integrated error handling +// 1 : PclError external library error handling. By enabling this +// you must ensure that you have included PclError library. +// [2,...] : reserved for futur use +if (!defined('PCLZIP_ERROR_EXTERNAL')) { + define('PCLZIP_ERROR_EXTERNAL', 0); +} + +// ----- Optional static temporary directory +// By default temporary files are generated in the script current +// path. +// If defined : +// - MUST BE terminated by a '/'. +// - MUST be a valid, already created directory +// Samples : +// define( 'PCLZIP_TEMPORARY_DIR', '/temp/' ); +// define( 'PCLZIP_TEMPORARY_DIR', 'C:/Temp/' ); +if (!defined('PCLZIP_TEMPORARY_DIR')) { + define('PCLZIP_TEMPORARY_DIR', ''); +} + +// ----- Optional threshold ratio for use of temporary files +// Pclzip sense the size of the file to add/extract and decide to +// use or not temporary file. The algorythm is looking for +// memory_limit of PHP and apply a ratio. +// threshold = memory_limit * ratio. +// Recommended values are under 0.5. Default 0.47. +// Samples : +// define( 'PCLZIP_TEMPORARY_FILE_RATIO', 0.5 ); +if (!defined('PCLZIP_TEMPORARY_FILE_RATIO')) { + define('PCLZIP_TEMPORARY_FILE_RATIO', 0.47); +} // -------------------------------------------------------------------------------- // ***** UNDER THIS LINE NOTHING NEEDS TO BE MODIFIED ***** // -------------------------------------------------------------------------------- - // ----- Global variables - $g_pclzip_version = "2.8.2"; - - // ----- Error codes - // -1 : Unable to open file in binary write mode - // -2 : Unable to open file in binary read mode - // -3 : Invalid parameters - // -4 : File does not exist - // -5 : Filename is too long (max. 255) - // -6 : Not a valid zip file - // -7 : Invalid extracted file size - // -8 : Unable to create directory - // -9 : Invalid archive extension - // -10 : Invalid archive format - // -11 : Unable to delete file (unlink) - // -12 : Unable to rename file (rename) - // -13 : Invalid header checksum - // -14 : Invalid archive size - define( 'PCLZIP_ERR_USER_ABORTED', 2 ); - define( 'PCLZIP_ERR_NO_ERROR', 0 ); - define( 'PCLZIP_ERR_WRITE_OPEN_FAIL', -1 ); - define( 'PCLZIP_ERR_READ_OPEN_FAIL', -2 ); - define( 'PCLZIP_ERR_INVALID_PARAMETER', -3 ); - define( 'PCLZIP_ERR_MISSING_FILE', -4 ); - define( 'PCLZIP_ERR_FILENAME_TOO_LONG', -5 ); - define( 'PCLZIP_ERR_INVALID_ZIP', -6 ); - define( 'PCLZIP_ERR_BAD_EXTRACTED_FILE', -7 ); - define( 'PCLZIP_ERR_DIR_CREATE_FAIL', -8 ); - define( 'PCLZIP_ERR_BAD_EXTENSION', -9 ); - define( 'PCLZIP_ERR_BAD_FORMAT', -10 ); - define( 'PCLZIP_ERR_DELETE_FILE_FAIL', -11 ); - define( 'PCLZIP_ERR_RENAME_FILE_FAIL', -12 ); - define( 'PCLZIP_ERR_BAD_CHECKSUM', -13 ); - define( 'PCLZIP_ERR_INVALID_ARCHIVE_ZIP', -14 ); - define( 'PCLZIP_ERR_MISSING_OPTION_VALUE', -15 ); - define( 'PCLZIP_ERR_INVALID_OPTION_VALUE', -16 ); - define( 'PCLZIP_ERR_ALREADY_A_DIRECTORY', -17 ); - define( 'PCLZIP_ERR_UNSUPPORTED_COMPRESSION', -18 ); - define( 'PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', -19 ); - define( 'PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', -20 ); - define( 'PCLZIP_ERR_DIRECTORY_RESTRICTION', -21 ); - - // ----- Options values - define( 'PCLZIP_OPT_PATH', 77001 ); - define( 'PCLZIP_OPT_ADD_PATH', 77002 ); - define( 'PCLZIP_OPT_REMOVE_PATH', 77003 ); - define( 'PCLZIP_OPT_REMOVE_ALL_PATH', 77004 ); - define( 'PCLZIP_OPT_SET_CHMOD', 77005 ); - define( 'PCLZIP_OPT_EXTRACT_AS_STRING', 77006 ); - define( 'PCLZIP_OPT_NO_COMPRESSION', 77007 ); - define( 'PCLZIP_OPT_BY_NAME', 77008 ); - define( 'PCLZIP_OPT_BY_INDEX', 77009 ); - define( 'PCLZIP_OPT_BY_EREG', 77010 ); - define( 'PCLZIP_OPT_BY_PREG', 77011 ); - define( 'PCLZIP_OPT_COMMENT', 77012 ); - define( 'PCLZIP_OPT_ADD_COMMENT', 77013 ); - define( 'PCLZIP_OPT_PREPEND_COMMENT', 77014 ); - define( 'PCLZIP_OPT_EXTRACT_IN_OUTPUT', 77015 ); - define( 'PCLZIP_OPT_REPLACE_NEWER', 77016 ); - define( 'PCLZIP_OPT_STOP_ON_ERROR', 77017 ); - // Having big trouble with crypt. Need to multiply 2 long int - // which is not correctly supported by PHP ... - //define( 'PCLZIP_OPT_CRYPT', 77018 ); - define( 'PCLZIP_OPT_EXTRACT_DIR_RESTRICTION', 77019 ); - define( 'PCLZIP_OPT_TEMP_FILE_THRESHOLD', 77020 ); - define( 'PCLZIP_OPT_ADD_TEMP_FILE_THRESHOLD', 77020 ); // alias - define( 'PCLZIP_OPT_TEMP_FILE_ON', 77021 ); - define( 'PCLZIP_OPT_ADD_TEMP_FILE_ON', 77021 ); // alias - define( 'PCLZIP_OPT_TEMP_FILE_OFF', 77022 ); - define( 'PCLZIP_OPT_ADD_TEMP_FILE_OFF', 77022 ); // alias - - // ----- File description attributes - define( 'PCLZIP_ATT_FILE_NAME', 79001 ); - define( 'PCLZIP_ATT_FILE_NEW_SHORT_NAME', 79002 ); - define( 'PCLZIP_ATT_FILE_NEW_FULL_NAME', 79003 ); - define( 'PCLZIP_ATT_FILE_MTIME', 79004 ); - define( 'PCLZIP_ATT_FILE_CONTENT', 79005 ); - define( 'PCLZIP_ATT_FILE_COMMENT', 79006 ); - - // ----- Call backs values - define( 'PCLZIP_CB_PRE_EXTRACT', 78001 ); - define( 'PCLZIP_CB_POST_EXTRACT', 78002 ); - define( 'PCLZIP_CB_PRE_ADD', 78003 ); - define( 'PCLZIP_CB_POST_ADD', 78004 ); - /* For futur use - define( 'PCLZIP_CB_PRE_LIST', 78005 ); - define( 'PCLZIP_CB_POST_LIST', 78006 ); - define( 'PCLZIP_CB_PRE_DELETE', 78007 ); - define( 'PCLZIP_CB_POST_DELETE', 78008 ); - */ - - // -------------------------------------------------------------------------------- - // Class : PclZip - // Description : - // PclZip is the class that represent a Zip archive. - // The public methods allow the manipulation of the archive. - // Attributes : - // Attributes must not be accessed directly. - // Methods : - // PclZip() : Object creator - // create() : Creates the Zip archive - // listContent() : List the content of the Zip archive - // extract() : Extract the content of the archive - // properties() : List the properties of the archive - // -------------------------------------------------------------------------------- - class PclZip - { +// ----- Global variables +$g_pclzip_version = "2.8.2"; + +// ----- Error codes +// -1 : Unable to open file in binary write mode +// -2 : Unable to open file in binary read mode +// -3 : Invalid parameters +// -4 : File does not exist +// -5 : Filename is too long (max. 255) +// -6 : Not a valid zip file +// -7 : Invalid extracted file size +// -8 : Unable to create directory +// -9 : Invalid archive extension +// -10 : Invalid archive format +// -11 : Unable to delete file (unlink) +// -12 : Unable to rename file (rename) +// -13 : Invalid header checksum +// -14 : Invalid archive size +define('PCLZIP_ERR_USER_ABORTED', 2); +define('PCLZIP_ERR_NO_ERROR', 0); +define('PCLZIP_ERR_WRITE_OPEN_FAIL', -1); +define('PCLZIP_ERR_READ_OPEN_FAIL', -2); +define('PCLZIP_ERR_INVALID_PARAMETER', -3); +define('PCLZIP_ERR_MISSING_FILE', -4); +define('PCLZIP_ERR_FILENAME_TOO_LONG', -5); +define('PCLZIP_ERR_INVALID_ZIP', -6); +define('PCLZIP_ERR_BAD_EXTRACTED_FILE', -7); +define('PCLZIP_ERR_DIR_CREATE_FAIL', -8); +define('PCLZIP_ERR_BAD_EXTENSION', -9); +define('PCLZIP_ERR_BAD_FORMAT', -10); +define('PCLZIP_ERR_DELETE_FILE_FAIL', -11); +define('PCLZIP_ERR_RENAME_FILE_FAIL', -12); +define('PCLZIP_ERR_BAD_CHECKSUM', -13); +define('PCLZIP_ERR_INVALID_ARCHIVE_ZIP', -14); +define('PCLZIP_ERR_MISSING_OPTION_VALUE', -15); +define('PCLZIP_ERR_INVALID_OPTION_VALUE', -16); +define('PCLZIP_ERR_ALREADY_A_DIRECTORY', -17); +define('PCLZIP_ERR_UNSUPPORTED_COMPRESSION', -18); +define('PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', -19); +define('PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', -20); +define('PCLZIP_ERR_DIRECTORY_RESTRICTION', -21); + +// ----- Options values +define('PCLZIP_OPT_PATH', 77001); +define('PCLZIP_OPT_ADD_PATH', 77002); +define('PCLZIP_OPT_REMOVE_PATH', 77003); +define('PCLZIP_OPT_REMOVE_ALL_PATH', 77004); +define('PCLZIP_OPT_SET_CHMOD', 77005); +define('PCLZIP_OPT_EXTRACT_AS_STRING', 77006); +define('PCLZIP_OPT_NO_COMPRESSION', 77007); +define('PCLZIP_OPT_BY_NAME', 77008); +define('PCLZIP_OPT_BY_INDEX', 77009); +define('PCLZIP_OPT_BY_EREG', 77010); +define('PCLZIP_OPT_BY_PREG', 77011); +define('PCLZIP_OPT_COMMENT', 77012); +define('PCLZIP_OPT_ADD_COMMENT', 77013); +define('PCLZIP_OPT_PREPEND_COMMENT', 77014); +define('PCLZIP_OPT_EXTRACT_IN_OUTPUT', 77015); +define('PCLZIP_OPT_REPLACE_NEWER', 77016); +define('PCLZIP_OPT_STOP_ON_ERROR', 77017); +// Having big trouble with crypt. Need to multiply 2 long int +// which is not correctly supported by PHP ... +//define( 'PCLZIP_OPT_CRYPT', 77018 ); +define('PCLZIP_OPT_EXTRACT_DIR_RESTRICTION', 77019); +define('PCLZIP_OPT_TEMP_FILE_THRESHOLD', 77020); +define('PCLZIP_OPT_ADD_TEMP_FILE_THRESHOLD', 77020); // alias +define('PCLZIP_OPT_TEMP_FILE_ON', 77021); +define('PCLZIP_OPT_ADD_TEMP_FILE_ON', 77021); // alias +define('PCLZIP_OPT_TEMP_FILE_OFF', 77022); +define('PCLZIP_OPT_ADD_TEMP_FILE_OFF', 77022); // alias + +// ----- File description attributes +define('PCLZIP_ATT_FILE_NAME', 79001); +define('PCLZIP_ATT_FILE_NEW_SHORT_NAME', 79002); +define('PCLZIP_ATT_FILE_NEW_FULL_NAME', 79003); +define('PCLZIP_ATT_FILE_MTIME', 79004); +define('PCLZIP_ATT_FILE_CONTENT', 79005); +define('PCLZIP_ATT_FILE_COMMENT', 79006); + +// ----- Call backs values +define('PCLZIP_CB_PRE_EXTRACT', 78001); +define('PCLZIP_CB_POST_EXTRACT', 78002); +define('PCLZIP_CB_PRE_ADD', 78003); +define('PCLZIP_CB_POST_ADD', 78004); +/* For futur use +define( 'PCLZIP_CB_PRE_LIST', 78005 ); +define( 'PCLZIP_CB_POST_LIST', 78006 ); +define( 'PCLZIP_CB_PRE_DELETE', 78007 ); +define( 'PCLZIP_CB_POST_DELETE', 78008 ); +*/ + +// -------------------------------------------------------------------------------- +// Class : PclZip +// Description : +// PclZip is the class that represent a Zip archive. +// The public methods allow the manipulation of the archive. +// Attributes : +// Attributes must not be accessed directly. +// Methods : +// PclZip() : Object creator +// create() : Creates the Zip archive +// listContent() : List the content of the Zip archive +// extract() : Extract the content of the archive +// properties() : List the properties of the archive +// -------------------------------------------------------------------------------- +class PclZip +{ // ----- Filename of the zip file - var $zipname = ''; + public $zipname = ''; // ----- File descriptor of the zip file - var $zip_fd = 0; + public $zip_fd = 0; // ----- Internal error handling - var $error_code = 1; - var $error_string = ''; + public $error_code = 1; + public $error_string = ''; // ----- Current status of the magic_quotes_runtime // This value store the php configuration for magic_quotes // The class can then disable the magic_quotes and reset it after - var $magic_quotes_status; - - // -------------------------------------------------------------------------------- - // Function : PclZip() - // Description : - // Creates a PclZip object and set the name of the associated Zip archive - // filename. - // Note that no real action is taken, if the archive does not exist it is not - // created. Use create() for that. - // -------------------------------------------------------------------------------- - function PclZip($p_zipname) - { - - // ----- Tests the zlib - if (!function_exists('gzopen')) + public $magic_quotes_status; + + // -------------------------------------------------------------------------------- + // Function : PclZip() + // Description : + // Creates a PclZip object and set the name of the associated Zip archive + // filename. + // Note that no real action is taken, if the archive does not exist it is not + // created. Use create() for that. + // -------------------------------------------------------------------------------- + public function __construct($p_zipname) { - die('Abort '.basename(__FILE__).' : Missing zlib extensions'); - } - - // ----- Set the attributes - $this->zipname = $p_zipname; - $this->zip_fd = 0; - $this->magic_quotes_status = -1; - // ----- Return - return; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : - // create($p_filelist, $p_add_dir="", $p_remove_dir="") - // create($p_filelist, $p_option, $p_option_value, ...) - // Description : - // This method supports two different synopsis. The first one is historical. - // This method creates a Zip Archive. The Zip file is created in the - // filesystem. The files and directories indicated in $p_filelist - // are added in the archive. See the parameters description for the - // supported format of $p_filelist. - // When a directory is in the list, the directory and its content is added - // in the archive. - // In this synopsis, the function takes an optional variable list of - // options. See bellow the supported options. - // Parameters : - // $p_filelist : An array containing file or directory names, or - // a string containing one filename or one directory name, or - // a string containing a list of filenames and/or directory - // names separated by spaces. - // $p_add_dir : A path to add before the real path of the archived file, - // in order to have it memorized in the archive. - // $p_remove_dir : A path to remove from the real path of the file to archive, - // in order to have a shorter path memorized in the archive. - // When $p_add_dir and $p_remove_dir are set, $p_remove_dir - // is removed first, before $p_add_dir is added. - // Options : - // PCLZIP_OPT_ADD_PATH : - // PCLZIP_OPT_REMOVE_PATH : - // PCLZIP_OPT_REMOVE_ALL_PATH : - // PCLZIP_OPT_COMMENT : - // PCLZIP_CB_PRE_ADD : - // PCLZIP_CB_POST_ADD : - // Return Values : - // 0 on failure, - // The list of the added files, with a status of the add action. - // (see PclZip::listContent() for list entry format) - // -------------------------------------------------------------------------------- - function create($p_filelist) - { - $v_result=1; - - // ----- Reset the error handler - $this->privErrorReset(); - - // ----- Set default values - $v_options = array(); - $v_options[PCLZIP_OPT_NO_COMPRESSION] = FALSE; - - // ----- Look for variable options arguments - $v_size = func_num_args(); - - // ----- Look for arguments - if ($v_size > 1) { - // ----- Get the arguments - $v_arg_list = func_get_args(); - - // ----- Remove from the options list the first argument - array_shift($v_arg_list); - $v_size--; - - // ----- Look for first arg - if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { - - // ----- Parse the options - $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, - array (PCLZIP_OPT_REMOVE_PATH => 'optional', - PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', - PCLZIP_OPT_ADD_PATH => 'optional', - PCLZIP_CB_PRE_ADD => 'optional', - PCLZIP_CB_POST_ADD => 'optional', - PCLZIP_OPT_NO_COMPRESSION => 'optional', - PCLZIP_OPT_COMMENT => 'optional', - PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', - PCLZIP_OPT_TEMP_FILE_ON => 'optional', - PCLZIP_OPT_TEMP_FILE_OFF => 'optional' - //, PCLZIP_OPT_CRYPT => 'optional' - )); - if ($v_result != 1) { - return 0; + // ----- Tests the zlib + if (!function_exists('gzopen')) { + die('Abort ' . basename(__FILE__) . ' : Missing zlib extensions'); } - } - // ----- Look for 2 args - // Here we need to support the first historic synopsis of the - // method. - else { + // ----- Set the attributes + $this->zipname = $p_zipname; + $this->zip_fd = 0; + $this->magic_quotes_status = -1; - // ----- Get the first argument - $v_options[PCLZIP_OPT_ADD_PATH] = $v_arg_list[0]; + // ----- Return + return; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // create($p_filelist, $p_add_dir="", $p_remove_dir="") + // create($p_filelist, $p_option, $p_option_value, ...) + // Description : + // This method supports two different synopsis. The first one is historical. + // This method creates a Zip Archive. The Zip file is created in the + // filesystem. The files and directories indicated in $p_filelist + // are added in the archive. See the parameters description for the + // supported format of $p_filelist. + // When a directory is in the list, the directory and its content is added + // in the archive. + // In this synopsis, the function takes an optional variable list of + // options. See bellow the supported options. + // Parameters : + // $p_filelist : An array containing file or directory names, or + // a string containing one filename or one directory name, or + // a string containing a list of filenames and/or directory + // names separated by spaces. + // $p_add_dir : A path to add before the real path of the archived file, + // in order to have it memorized in the archive. + // $p_remove_dir : A path to remove from the real path of the file to archive, + // in order to have a shorter path memorized in the archive. + // When $p_add_dir and $p_remove_dir are set, $p_remove_dir + // is removed first, before $p_add_dir is added. + // Options : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_OPT_COMMENT : + // PCLZIP_CB_PRE_ADD : + // PCLZIP_CB_POST_ADD : + // Return Values : + // 0 on failure, + // The list of the added files, with a status of the add action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + public function create($p_filelist) + { + $v_result = 1; - // ----- Look for the optional second argument - if ($v_size == 2) { - $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1]; - } - else if ($v_size > 2) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, - "Invalid number / type of arguments"); - return 0; + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Set default values + $v_options = array(); + $v_options[PCLZIP_OPT_NO_COMPRESSION] = false; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Look for arguments + if ($v_size > 1) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Remove from the options list the first argument + array_shift($v_arg_list); + $v_size--; + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, array( + PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_ADD => 'optional', + PCLZIP_CB_POST_ADD => 'optional', + PCLZIP_OPT_NO_COMPRESSION => 'optional', + PCLZIP_OPT_COMMENT => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + //, PCLZIP_OPT_CRYPT => 'optional' + )); + if ($v_result != 1) { + return 0; + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + } else { + + // ----- Get the first argument + $v_options[PCLZIP_OPT_ADD_PATH] = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1]; + } elseif ($v_size > 2) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + + return 0; + } + } } - } - } - // ----- Look for default option values - $this->privOptionDefaultThreshold($v_options); + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); - // ----- Init - $v_string_list = array(); - $v_att_list = array(); - $v_filedescr_list = array(); - $p_result_list = array(); + // ----- Init + $v_string_list = array(); + $v_att_list = array(); + $v_filedescr_list = array(); + $p_result_list = array(); - // ----- Look if the $p_filelist is really an array - if (is_array($p_filelist)) { + // ----- Look if the $p_filelist is really an array + if (is_array($p_filelist)) { - // ----- Look if the first element is also an array - // This will mean that this is a file description entry - if (isset($p_filelist[0]) && is_array($p_filelist[0])) { - $v_att_list = $p_filelist; - } + // ----- Look if the first element is also an array + // This will mean that this is a file description entry + if (isset($p_filelist[0]) && is_array($p_filelist[0])) { + $v_att_list = $p_filelist; - // ----- The list is a list of string names - else { - $v_string_list = $p_filelist; - } - } + // ----- The list is a list of string names + } else { + $v_string_list = $p_filelist; + } - // ----- Look if the $p_filelist is a string - else if (is_string($p_filelist)) { - // ----- Create a list from the string - $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist); - } + // ----- Look if the $p_filelist is a string + } elseif (is_string($p_filelist)) { + // ----- Create a list from the string + $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist); - // ----- Invalid variable type for $p_filelist - else { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_filelist"); - return 0; - } + // ----- Invalid variable type for $p_filelist + } else { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_filelist"); - // ----- Reformat the string list - if (sizeof($v_string_list) != 0) { - foreach ($v_string_list as $v_string) { - if ($v_string != '') { - $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string; + return 0; } - else { - } - } - } - // ----- For each file in the list check the attributes - $v_supported_attributes - = array ( PCLZIP_ATT_FILE_NAME => 'mandatory' - ,PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional' - ,PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional' - ,PCLZIP_ATT_FILE_MTIME => 'optional' - ,PCLZIP_ATT_FILE_CONTENT => 'optional' - ,PCLZIP_ATT_FILE_COMMENT => 'optional' - ); - foreach ($v_att_list as $v_entry) { - $v_result = $this->privFileDescrParseAtt($v_entry, - $v_filedescr_list[], - $v_options, - $v_supported_attributes); - if ($v_result != 1) { - return 0; - } - } - - // ----- Expand the filelist (expand directories) - $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options); - if ($v_result != 1) { - return 0; - } + // ----- Reformat the string list + if (sizeof($v_string_list) != 0) { + foreach ($v_string_list as $v_string) { + if ($v_string != '') { + $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string; + } else { + } + } + } - // ----- Call the create fct - $v_result = $this->privCreate($v_filedescr_list, $p_result_list, $v_options); - if ($v_result != 1) { - return 0; - } + // ----- For each file in the list check the attributes + $v_supported_attributes = array( + PCLZIP_ATT_FILE_NAME => 'mandatory', + PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional', + PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional', + PCLZIP_ATT_FILE_MTIME => 'optional', + PCLZIP_ATT_FILE_CONTENT => 'optional', + PCLZIP_ATT_FILE_COMMENT => 'optional' + ); + foreach ($v_att_list as $v_entry) { + $v_result = $this->privFileDescrParseAtt($v_entry, $v_filedescr_list[], $v_options, $v_supported_attributes); + if ($v_result != 1) { + return 0; + } + } - // ----- Return - return $p_result_list; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : - // add($p_filelist, $p_add_dir="", $p_remove_dir="") - // add($p_filelist, $p_option, $p_option_value, ...) - // Description : - // This method supports two synopsis. The first one is historical. - // This methods add the list of files in an existing archive. - // If a file with the same name already exists, it is added at the end of the - // archive, the first one is still present. - // If the archive does not exist, it is created. - // Parameters : - // $p_filelist : An array containing file or directory names, or - // a string containing one filename or one directory name, or - // a string containing a list of filenames and/or directory - // names separated by spaces. - // $p_add_dir : A path to add before the real path of the archived file, - // in order to have it memorized in the archive. - // $p_remove_dir : A path to remove from the real path of the file to archive, - // in order to have a shorter path memorized in the archive. - // When $p_add_dir and $p_remove_dir are set, $p_remove_dir - // is removed first, before $p_add_dir is added. - // Options : - // PCLZIP_OPT_ADD_PATH : - // PCLZIP_OPT_REMOVE_PATH : - // PCLZIP_OPT_REMOVE_ALL_PATH : - // PCLZIP_OPT_COMMENT : - // PCLZIP_OPT_ADD_COMMENT : - // PCLZIP_OPT_PREPEND_COMMENT : - // PCLZIP_CB_PRE_ADD : - // PCLZIP_CB_POST_ADD : - // Return Values : - // 0 on failure, - // The list of the added files, with a status of the add action. - // (see PclZip::listContent() for list entry format) - // -------------------------------------------------------------------------------- - function add($p_filelist) - { - $v_result=1; - - // ----- Reset the error handler - $this->privErrorReset(); - - // ----- Set default values - $v_options = array(); - $v_options[PCLZIP_OPT_NO_COMPRESSION] = FALSE; - - // ----- Look for variable options arguments - $v_size = func_num_args(); - - // ----- Look for arguments - if ($v_size > 1) { - // ----- Get the arguments - $v_arg_list = func_get_args(); - - // ----- Remove form the options list the first argument - array_shift($v_arg_list); - $v_size--; - - // ----- Look for first arg - if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { - - // ----- Parse the options - $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, - array (PCLZIP_OPT_REMOVE_PATH => 'optional', - PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', - PCLZIP_OPT_ADD_PATH => 'optional', - PCLZIP_CB_PRE_ADD => 'optional', - PCLZIP_CB_POST_ADD => 'optional', - PCLZIP_OPT_NO_COMPRESSION => 'optional', - PCLZIP_OPT_COMMENT => 'optional', - PCLZIP_OPT_ADD_COMMENT => 'optional', - PCLZIP_OPT_PREPEND_COMMENT => 'optional', - PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', - PCLZIP_OPT_TEMP_FILE_ON => 'optional', - PCLZIP_OPT_TEMP_FILE_OFF => 'optional' - //, PCLZIP_OPT_CRYPT => 'optional' - )); + // ----- Expand the filelist (expand directories) + $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options); if ($v_result != 1) { - return 0; + return 0; } - } - - // ----- Look for 2 args - // Here we need to support the first historic synopsis of the - // method. - else { - // ----- Get the first argument - $v_options[PCLZIP_OPT_ADD_PATH] = $v_add_path = $v_arg_list[0]; - - // ----- Look for the optional second argument - if ($v_size == 2) { - $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1]; + // ----- Call the create fct + $v_result = $this->privCreate($v_filedescr_list, $p_result_list, $v_options); + if ($v_result != 1) { + return 0; } - else if ($v_size > 2) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); - // ----- Return - return 0; + // ----- Return + return $p_result_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // add($p_filelist, $p_add_dir="", $p_remove_dir="") + // add($p_filelist, $p_option, $p_option_value, ...) + // Description : + // This method supports two synopsis. The first one is historical. + // This methods add the list of files in an existing archive. + // If a file with the same name already exists, it is added at the end of the + // archive, the first one is still present. + // If the archive does not exist, it is created. + // Parameters : + // $p_filelist : An array containing file or directory names, or + // a string containing one filename or one directory name, or + // a string containing a list of filenames and/or directory + // names separated by spaces. + // $p_add_dir : A path to add before the real path of the archived file, + // in order to have it memorized in the archive. + // $p_remove_dir : A path to remove from the real path of the file to archive, + // in order to have a shorter path memorized in the archive. + // When $p_add_dir and $p_remove_dir are set, $p_remove_dir + // is removed first, before $p_add_dir is added. + // Options : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_OPT_COMMENT : + // PCLZIP_OPT_ADD_COMMENT : + // PCLZIP_OPT_PREPEND_COMMENT : + // PCLZIP_CB_PRE_ADD : + // PCLZIP_CB_POST_ADD : + // Return Values : + // 0 on failure, + // The list of the added files, with a status of the add action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + public function add($p_filelist) + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Set default values + $v_options = array(); + $v_options[PCLZIP_OPT_NO_COMPRESSION] = false; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Look for arguments + if ($v_size > 1) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Remove form the options list the first argument + array_shift($v_arg_list); + $v_size--; + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, array( + PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_ADD => 'optional', + PCLZIP_CB_POST_ADD => 'optional', + PCLZIP_OPT_NO_COMPRESSION => 'optional', + PCLZIP_OPT_COMMENT => 'optional', + PCLZIP_OPT_ADD_COMMENT => 'optional', + PCLZIP_OPT_PREPEND_COMMENT => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + //, PCLZIP_OPT_CRYPT => 'optional' + )); + if ($v_result != 1) { + return 0; + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + } else { + + // ----- Get the first argument + $v_options[PCLZIP_OPT_ADD_PATH] = $v_add_path = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1]; + } elseif ($v_size > 2) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + + // ----- Return + return 0; + } + } } - } - } - // ----- Look for default option values - $this->privOptionDefaultThreshold($v_options); + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); - // ----- Init - $v_string_list = array(); - $v_att_list = array(); - $v_filedescr_list = array(); - $p_result_list = array(); + // ----- Init + $v_string_list = array(); + $v_att_list = array(); + $v_filedescr_list = array(); + $p_result_list = array(); - // ----- Look if the $p_filelist is really an array - if (is_array($p_filelist)) { + // ----- Look if the $p_filelist is really an array + if (is_array($p_filelist)) { - // ----- Look if the first element is also an array - // This will mean that this is a file description entry - if (isset($p_filelist[0]) && is_array($p_filelist[0])) { - $v_att_list = $p_filelist; - } + // ----- Look if the first element is also an array + // This will mean that this is a file description entry + if (isset($p_filelist[0]) && is_array($p_filelist[0])) { + $v_att_list = $p_filelist; - // ----- The list is a list of string names - else { - $v_string_list = $p_filelist; - } - } + // ----- The list is a list of string names + } else { + $v_string_list = $p_filelist; + } - // ----- Look if the $p_filelist is a string - else if (is_string($p_filelist)) { - // ----- Create a list from the string - $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist); - } + // ----- Look if the $p_filelist is a string + } elseif (is_string($p_filelist)) { + // ----- Create a list from the string + $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist); - // ----- Invalid variable type for $p_filelist - else { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type '".gettype($p_filelist)."' for p_filelist"); - return 0; - } + // ----- Invalid variable type for $p_filelist + } else { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type '" . gettype($p_filelist) . "' for p_filelist"); - // ----- Reformat the string list - if (sizeof($v_string_list) != 0) { - foreach ($v_string_list as $v_string) { - $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string; - } - } + return 0; + } - // ----- For each file in the list check the attributes - $v_supported_attributes - = array ( PCLZIP_ATT_FILE_NAME => 'mandatory' - ,PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional' - ,PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional' - ,PCLZIP_ATT_FILE_MTIME => 'optional' - ,PCLZIP_ATT_FILE_CONTENT => 'optional' - ,PCLZIP_ATT_FILE_COMMENT => 'optional' - ); - foreach ($v_att_list as $v_entry) { - $v_result = $this->privFileDescrParseAtt($v_entry, - $v_filedescr_list[], - $v_options, - $v_supported_attributes); - if ($v_result != 1) { - return 0; - } - } + // ----- Reformat the string list + if (sizeof($v_string_list) != 0) { + foreach ($v_string_list as $v_string) { + $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string; + } + } - // ----- Expand the filelist (expand directories) - $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options); - if ($v_result != 1) { - return 0; - } + // ----- For each file in the list check the attributes + $v_supported_attributes = array( + PCLZIP_ATT_FILE_NAME => 'mandatory', + PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional', + PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional', + PCLZIP_ATT_FILE_MTIME => 'optional', + PCLZIP_ATT_FILE_CONTENT => 'optional', + PCLZIP_ATT_FILE_COMMENT => 'optional' + ); + foreach ($v_att_list as $v_entry) { + $v_result = $this->privFileDescrParseAtt($v_entry, $v_filedescr_list[], $v_options, $v_supported_attributes); + if ($v_result != 1) { + return 0; + } + } - // ----- Call the create fct - $v_result = $this->privAdd($v_filedescr_list, $p_result_list, $v_options); - if ($v_result != 1) { - return 0; - } + // ----- Expand the filelist (expand directories) + $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options); + if ($v_result != 1) { + return 0; + } - // ----- Return - return $p_result_list; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : listContent() - // Description : - // This public method, gives the list of the files and directories, with their - // properties. - // The properties of each entries in the list are (used also in other functions) : - // filename : Name of the file. For a create or add action it is the filename - // given by the user. For an extract function it is the filename - // of the extracted file. - // stored_filename : Name of the file / directory stored in the archive. - // size : Size of the stored file. - // compressed_size : Size of the file's data compressed in the archive - // (without the headers overhead) - // mtime : Last known modification date of the file (UNIX timestamp) - // comment : Comment associated with the file - // folder : true | false - // index : index of the file in the archive - // status : status of the action (depending of the action) : - // Values are : - // ok : OK ! - // filtered : the file / dir is not extracted (filtered by user) - // already_a_directory : the file can not be extracted because a - // directory with the same name already exists - // write_protected : the file can not be extracted because a file - // with the same name already exists and is - // write protected - // newer_exist : the file was not extracted because a newer file exists - // path_creation_fail : the file is not extracted because the folder - // does not exist and can not be created - // write_error : the file was not extracted because there was a - // error while writing the file - // read_error : the file was not extracted because there was a error - // while reading the file - // invalid_header : the file was not extracted because of an archive - // format error (bad file header) - // Note that each time a method can continue operating when there - // is an action error on a file, the error is only logged in the file status. - // Return Values : - // 0 on an unrecoverable failure, - // The list of the files in the archive. - // -------------------------------------------------------------------------------- - function listContent() - { - $v_result=1; - - // ----- Reset the error handler - $this->privErrorReset(); - - // ----- Check archive - if (!$this->privCheckFormat()) { - return(0); - } + // ----- Call the create fct + $v_result = $this->privAdd($v_filedescr_list, $p_result_list, $v_options); + if ($v_result != 1) { + return 0; + } - // ----- Call the extracting fct - $p_list = array(); - if (($v_result = $this->privList($p_list)) != 1) + // ----- Return + return $p_result_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : listContent() + // Description : + // This public method, gives the list of the files and directories, with their + // properties. + // The properties of each entries in the list are (used also in other functions) : + // filename : Name of the file. For a create or add action it is the filename + // given by the user. For an extract function it is the filename + // of the extracted file. + // stored_filename : Name of the file / directory stored in the archive. + // size : Size of the stored file. + // compressed_size : Size of the file's data compressed in the archive + // (without the headers overhead) + // mtime : Last known modification date of the file (UNIX timestamp) + // comment : Comment associated with the file + // folder : true | false + // index : index of the file in the archive + // status : status of the action (depending of the action) : + // Values are : + // ok : OK ! + // filtered : the file / dir is not extracted (filtered by user) + // already_a_directory : the file can not be extracted because a + // directory with the same name already exists + // write_protected : the file can not be extracted because a file + // with the same name already exists and is + // write protected + // newer_exist : the file was not extracted because a newer file exists + // path_creation_fail : the file is not extracted because the folder + // does not exist and can not be created + // write_error : the file was not extracted because there was a + // error while writing the file + // read_error : the file was not extracted because there was a error + // while reading the file + // invalid_header : the file was not extracted because of an archive + // format error (bad file header) + // Note that each time a method can continue operating when there + // is an action error on a file, the error is only logged in the file status. + // Return Values : + // 0 on an unrecoverable failure, + // The list of the files in the archive. + // -------------------------------------------------------------------------------- + public function listContent() { - unset($p_list); - return(0); - } + $v_result = 1; - // ----- Return - return $p_list; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : - // extract($p_path="./", $p_remove_path="") - // extract([$p_option, $p_option_value, ...]) - // Description : - // This method supports two synopsis. The first one is historical. - // This method extract all the files / directories from the archive to the - // folder indicated in $p_path. - // If you want to ignore the 'root' part of path of the memorized files - // you can indicate this in the optional $p_remove_path parameter. - // By default, if a newer file with the same name already exists, the - // file is not extracted. - // - // If both PCLZIP_OPT_PATH and PCLZIP_OPT_ADD_PATH aoptions - // are used, the path indicated in PCLZIP_OPT_ADD_PATH is append - // at the end of the path value of PCLZIP_OPT_PATH. - // Parameters : - // $p_path : Path where the files and directories are to be extracted - // $p_remove_path : First part ('root' part) of the memorized path - // (if any similar) to remove while extracting. - // Options : - // PCLZIP_OPT_PATH : - // PCLZIP_OPT_ADD_PATH : - // PCLZIP_OPT_REMOVE_PATH : - // PCLZIP_OPT_REMOVE_ALL_PATH : - // PCLZIP_CB_PRE_EXTRACT : - // PCLZIP_CB_POST_EXTRACT : - // Return Values : - // 0 or a negative value on failure, - // The list of the extracted files, with a status of the action. - // (see PclZip::listContent() for list entry format) - // -------------------------------------------------------------------------------- - function extract() - { - $v_result=1; - - // ----- Reset the error handler - $this->privErrorReset(); - - // ----- Check archive - if (!$this->privCheckFormat()) { - return(0); - } + // ----- Reset the error handler + $this->privErrorReset(); - // ----- Set default values - $v_options = array(); -// $v_path = "./"; - $v_path = ''; - $v_remove_path = ""; - $v_remove_all_path = false; - - // ----- Look for variable options arguments - $v_size = func_num_args(); - - // ----- Default values for option - $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE; - - // ----- Look for arguments - if ($v_size > 0) { - // ----- Get the arguments - $v_arg_list = func_get_args(); - - // ----- Look for first arg - if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { - - // ----- Parse the options - $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, - array (PCLZIP_OPT_PATH => 'optional', - PCLZIP_OPT_REMOVE_PATH => 'optional', - PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', - PCLZIP_OPT_ADD_PATH => 'optional', - PCLZIP_CB_PRE_EXTRACT => 'optional', - PCLZIP_CB_POST_EXTRACT => 'optional', - PCLZIP_OPT_SET_CHMOD => 'optional', - PCLZIP_OPT_BY_NAME => 'optional', - PCLZIP_OPT_BY_EREG => 'optional', - PCLZIP_OPT_BY_PREG => 'optional', - PCLZIP_OPT_BY_INDEX => 'optional', - PCLZIP_OPT_EXTRACT_AS_STRING => 'optional', - PCLZIP_OPT_EXTRACT_IN_OUTPUT => 'optional', - PCLZIP_OPT_REPLACE_NEWER => 'optional' - ,PCLZIP_OPT_STOP_ON_ERROR => 'optional' - ,PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional', - PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', - PCLZIP_OPT_TEMP_FILE_ON => 'optional', - PCLZIP_OPT_TEMP_FILE_OFF => 'optional' - )); - if ($v_result != 1) { - return 0; + // ----- Check archive + if (!$this->privCheckFormat()) { + return (0); } - // ----- Set the arguments - if (isset($v_options[PCLZIP_OPT_PATH])) { - $v_path = $v_options[PCLZIP_OPT_PATH]; - } - if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) { - $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH]; - } - if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { - $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH]; - } - if (isset($v_options[PCLZIP_OPT_ADD_PATH])) { - // ----- Check for '/' in last path char - if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) { - $v_path .= '/'; - } - $v_path .= $v_options[PCLZIP_OPT_ADD_PATH]; + // ----- Call the extracting fct + $p_list = array(); + if (($v_result = $this->privList($p_list)) != 1) { + unset($p_list); + + return (0); } - } - // ----- Look for 2 args - // Here we need to support the first historic synopsis of the - // method. - else { + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // extract($p_path="./", $p_remove_path="") + // extract([$p_option, $p_option_value, ...]) + // Description : + // This method supports two synopsis. The first one is historical. + // This method extract all the files / directories from the archive to the + // folder indicated in $p_path. + // If you want to ignore the 'root' part of path of the memorized files + // you can indicate this in the optional $p_remove_path parameter. + // By default, if a newer file with the same name already exists, the + // file is not extracted. + // + // If both PCLZIP_OPT_PATH and PCLZIP_OPT_ADD_PATH aoptions + // are used, the path indicated in PCLZIP_OPT_ADD_PATH is append + // at the end of the path value of PCLZIP_OPT_PATH. + // Parameters : + // $p_path : Path where the files and directories are to be extracted + // $p_remove_path : First part ('root' part) of the memorized path + // (if any similar) to remove while extracting. + // Options : + // PCLZIP_OPT_PATH : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_CB_PRE_EXTRACT : + // PCLZIP_CB_POST_EXTRACT : + // Return Values : + // 0 or a negative value on failure, + // The list of the extracted files, with a status of the action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + public function extract() + { + $v_result = 1; - // ----- Get the first argument - $v_path = $v_arg_list[0]; + // ----- Reset the error handler + $this->privErrorReset(); - // ----- Look for the optional second argument - if ($v_size == 2) { - $v_remove_path = $v_arg_list[1]; + // ----- Check archive + if (!$this->privCheckFormat()) { + return (0); } - else if ($v_size > 2) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); - // ----- Return - return 0; + // ----- Set default values + $v_options = array(); + // $v_path = "./"; + $v_path = ''; + $v_remove_path = ""; + $v_remove_all_path = false; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Default values for option + $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = false; + + // ----- Look for arguments + if ($v_size > 0) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, array( + PCLZIP_OPT_PATH => 'optional', + PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_EXTRACT => 'optional', + PCLZIP_CB_POST_EXTRACT => 'optional', + PCLZIP_OPT_SET_CHMOD => 'optional', + PCLZIP_OPT_BY_NAME => 'optional', + PCLZIP_OPT_BY_EREG => 'optional', + PCLZIP_OPT_BY_PREG => 'optional', + PCLZIP_OPT_BY_INDEX => 'optional', + PCLZIP_OPT_EXTRACT_AS_STRING => 'optional', + PCLZIP_OPT_EXTRACT_IN_OUTPUT => 'optional', + PCLZIP_OPT_REPLACE_NEWER => 'optional', + PCLZIP_OPT_STOP_ON_ERROR => 'optional', + PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + )); + if ($v_result != 1) { + return 0; + } + + // ----- Set the arguments + if (isset($v_options[PCLZIP_OPT_PATH])) { + $v_path = $v_options[PCLZIP_OPT_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) { + $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { + $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH]; + } + if (isset($v_options[PCLZIP_OPT_ADD_PATH])) { + // ----- Check for '/' in last path char + if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) { + $v_path .= '/'; + } + $v_path .= $v_options[PCLZIP_OPT_ADD_PATH]; + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + } else { + + // ----- Get the first argument + $v_path = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_remove_path = $v_arg_list[1]; + } elseif ($v_size > 2) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + + // ----- Return + return 0; + } + } } - } - } - // ----- Look for default option values - $this->privOptionDefaultThreshold($v_options); + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); - // ----- Trace - - // ----- Call the extracting fct - $p_list = array(); - $v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, - $v_remove_all_path, $v_options); - if ($v_result < 1) { - unset($p_list); - return(0); - } + // ----- Trace - // ----- Return - return $p_list; - } - // -------------------------------------------------------------------------------- - - - // -------------------------------------------------------------------------------- - // Function : - // extractByIndex($p_index, $p_path="./", $p_remove_path="") - // extractByIndex($p_index, [$p_option, $p_option_value, ...]) - // Description : - // This method supports two synopsis. The first one is historical. - // This method is doing a partial extract of the archive. - // The extracted files or folders are identified by their index in the - // archive (from 0 to n). - // Note that if the index identify a folder, only the folder entry is - // extracted, not all the files included in the archive. - // Parameters : - // $p_index : A single index (integer) or a string of indexes of files to - // extract. The form of the string is "0,4-6,8-12" with only numbers - // and '-' for range or ',' to separate ranges. No spaces or ';' - // are allowed. - // $p_path : Path where the files and directories are to be extracted - // $p_remove_path : First part ('root' part) of the memorized path - // (if any similar) to remove while extracting. - // Options : - // PCLZIP_OPT_PATH : - // PCLZIP_OPT_ADD_PATH : - // PCLZIP_OPT_REMOVE_PATH : - // PCLZIP_OPT_REMOVE_ALL_PATH : - // PCLZIP_OPT_EXTRACT_AS_STRING : The files are extracted as strings and - // not as files. - // The resulting content is in a new field 'content' in the file - // structure. - // This option must be used alone (any other options are ignored). - // PCLZIP_CB_PRE_EXTRACT : - // PCLZIP_CB_POST_EXTRACT : - // Return Values : - // 0 on failure, - // The list of the extracted files, with a status of the action. - // (see PclZip::listContent() for list entry format) - // -------------------------------------------------------------------------------- - //function extractByIndex($p_index, options...) - function extractByIndex($p_index) - { - $v_result=1; - - // ----- Reset the error handler - $this->privErrorReset(); - - // ----- Check archive - if (!$this->privCheckFormat()) { - return(0); - } + // ----- Call the extracting fct + $p_list = array(); + $v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, $v_remove_all_path, $v_options); + if ($v_result < 1) { + unset($p_list); - // ----- Set default values - $v_options = array(); -// $v_path = "./"; - $v_path = ''; - $v_remove_path = ""; - $v_remove_all_path = false; - - // ----- Look for variable options arguments - $v_size = func_num_args(); - - // ----- Default values for option - $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE; - - // ----- Look for arguments - if ($v_size > 1) { - // ----- Get the arguments - $v_arg_list = func_get_args(); - - // ----- Remove form the options list the first argument - array_shift($v_arg_list); - $v_size--; - - // ----- Look for first arg - if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { - - // ----- Parse the options - $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, - array (PCLZIP_OPT_PATH => 'optional', - PCLZIP_OPT_REMOVE_PATH => 'optional', - PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', - PCLZIP_OPT_EXTRACT_AS_STRING => 'optional', - PCLZIP_OPT_ADD_PATH => 'optional', - PCLZIP_CB_PRE_EXTRACT => 'optional', - PCLZIP_CB_POST_EXTRACT => 'optional', - PCLZIP_OPT_SET_CHMOD => 'optional', - PCLZIP_OPT_REPLACE_NEWER => 'optional' - ,PCLZIP_OPT_STOP_ON_ERROR => 'optional' - ,PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional', - PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', - PCLZIP_OPT_TEMP_FILE_ON => 'optional', - PCLZIP_OPT_TEMP_FILE_OFF => 'optional' - )); - if ($v_result != 1) { - return 0; + return (0); } - // ----- Set the arguments - if (isset($v_options[PCLZIP_OPT_PATH])) { - $v_path = $v_options[PCLZIP_OPT_PATH]; + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + + // -------------------------------------------------------------------------------- + // Function : + // extractByIndex($p_index, $p_path="./", $p_remove_path="") + // extractByIndex($p_index, [$p_option, $p_option_value, ...]) + // Description : + // This method supports two synopsis. The first one is historical. + // This method is doing a partial extract of the archive. + // The extracted files or folders are identified by their index in the + // archive (from 0 to n). + // Note that if the index identify a folder, only the folder entry is + // extracted, not all the files included in the archive. + // Parameters : + // $p_index : A single index (integer) or a string of indexes of files to + // extract. The form of the string is "0,4-6,8-12" with only numbers + // and '-' for range or ',' to separate ranges. No spaces or ';' + // are allowed. + // $p_path : Path where the files and directories are to be extracted + // $p_remove_path : First part ('root' part) of the memorized path + // (if any similar) to remove while extracting. + // Options : + // PCLZIP_OPT_PATH : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_OPT_EXTRACT_AS_STRING : The files are extracted as strings and + // not as files. + // The resulting content is in a new field 'content' in the file + // structure. + // This option must be used alone (any other options are ignored). + // PCLZIP_CB_PRE_EXTRACT : + // PCLZIP_CB_POST_EXTRACT : + // Return Values : + // 0 on failure, + // The list of the extracted files, with a status of the action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + //function extractByIndex($p_index, options...) + public function extractByIndex($p_index) + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return (0); } - if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) { - $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH]; + + // ----- Set default values + $v_options = array(); + // $v_path = "./"; + $v_path = ''; + $v_remove_path = ""; + $v_remove_all_path = false; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Default values for option + $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = false; + + // ----- Look for arguments + if ($v_size > 1) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Remove form the options list the first argument + array_shift($v_arg_list); + $v_size--; + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, array( + PCLZIP_OPT_PATH => 'optional', + PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_EXTRACT_AS_STRING => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_EXTRACT => 'optional', + PCLZIP_CB_POST_EXTRACT => 'optional', + PCLZIP_OPT_SET_CHMOD => 'optional', + PCLZIP_OPT_REPLACE_NEWER => 'optional', + PCLZIP_OPT_STOP_ON_ERROR => 'optional', + PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + )); + if ($v_result != 1) { + return 0; + } + + // ----- Set the arguments + if (isset($v_options[PCLZIP_OPT_PATH])) { + $v_path = $v_options[PCLZIP_OPT_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) { + $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { + $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH]; + } + if (isset($v_options[PCLZIP_OPT_ADD_PATH])) { + // ----- Check for '/' in last path char + if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) { + $v_path .= '/'; + } + $v_path .= $v_options[PCLZIP_OPT_ADD_PATH]; + } + if (!isset($v_options[PCLZIP_OPT_EXTRACT_AS_STRING])) { + $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = false; + } else { + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + } else { + + // ----- Get the first argument + $v_path = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_remove_path = $v_arg_list[1]; + } elseif ($v_size > 2) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + + // ----- Return + return 0; + } + } } - if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { - $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH]; + + // ----- Trace + + // ----- Trick + // Here I want to reuse extractByRule(), so I need to parse the $p_index + // with privParseOptions() + $v_arg_trick = array( + PCLZIP_OPT_BY_INDEX, + $p_index + ); + $v_options_trick = array(); + $v_result = $this->privParseOptions($v_arg_trick, sizeof($v_arg_trick), $v_options_trick, array( + PCLZIP_OPT_BY_INDEX => 'optional' + )); + if ($v_result != 1) { + return 0; } - if (isset($v_options[PCLZIP_OPT_ADD_PATH])) { - // ----- Check for '/' in last path char - if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) { - $v_path .= '/'; - } - $v_path .= $v_options[PCLZIP_OPT_ADD_PATH]; + $v_options[PCLZIP_OPT_BY_INDEX] = $v_options_trick[PCLZIP_OPT_BY_INDEX]; + + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); + + // ----- Call the extracting fct + if (($v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, $v_remove_all_path, $v_options)) < 1) { + return (0); } - if (!isset($v_options[PCLZIP_OPT_EXTRACT_AS_STRING])) { - $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE; + + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // delete([$p_option, $p_option_value, ...]) + // Description : + // This method removes files from the archive. + // If no parameters are given, then all the archive is emptied. + // Parameters : + // None or optional arguments. + // Options : + // PCLZIP_OPT_BY_INDEX : + // PCLZIP_OPT_BY_NAME : + // PCLZIP_OPT_BY_EREG : + // PCLZIP_OPT_BY_PREG : + // Return Values : + // 0 on failure, + // The list of the files which are still present in the archive. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + public function delete() + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return (0); } - else { + + // ----- Set default values + $v_options = array(); + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Look for arguments + if ($v_size > 0) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, array( + PCLZIP_OPT_BY_NAME => 'optional', + PCLZIP_OPT_BY_EREG => 'optional', + PCLZIP_OPT_BY_PREG => 'optional', + PCLZIP_OPT_BY_INDEX => 'optional' + )); + if ($v_result != 1) { + return 0; + } } - } - // ----- Look for 2 args - // Here we need to support the first historic synopsis of the - // method. - else { + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); - // ----- Get the first argument - $v_path = $v_arg_list[0]; + // ----- Call the delete fct + $v_list = array(); + if (($v_result = $this->privDeleteByRule($v_list, $v_options)) != 1) { + $this->privSwapBackMagicQuotes(); + unset($v_list); - // ----- Look for the optional second argument - if ($v_size == 2) { - $v_remove_path = $v_arg_list[1]; + return (0); } - else if ($v_size > 2) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); - // ----- Return - return 0; - } - } - } + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); - // ----- Trace - - // ----- Trick - // Here I want to reuse extractByRule(), so I need to parse the $p_index - // with privParseOptions() - $v_arg_trick = array (PCLZIP_OPT_BY_INDEX, $p_index); - $v_options_trick = array(); - $v_result = $this->privParseOptions($v_arg_trick, sizeof($v_arg_trick), $v_options_trick, - array (PCLZIP_OPT_BY_INDEX => 'optional' )); - if ($v_result != 1) { - return 0; + // ----- Return + return $v_list; } - $v_options[PCLZIP_OPT_BY_INDEX] = $v_options_trick[PCLZIP_OPT_BY_INDEX]; + // -------------------------------------------------------------------------------- - // ----- Look for default option values - $this->privOptionDefaultThreshold($v_options); + // -------------------------------------------------------------------------------- + // Function : deleteByIndex() + // Description : + // ***** Deprecated ***** + // delete(PCLZIP_OPT_BY_INDEX, $p_index) should be prefered. + // -------------------------------------------------------------------------------- + public function deleteByIndex($p_index) + { - // ----- Call the extracting fct - if (($v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, $v_remove_all_path, $v_options)) < 1) { - return(0); - } + $p_list = $this->delete(PCLZIP_OPT_BY_INDEX, $p_index); - // ----- Return - return $p_list; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : - // delete([$p_option, $p_option_value, ...]) - // Description : - // This method removes files from the archive. - // If no parameters are given, then all the archive is emptied. - // Parameters : - // None or optional arguments. - // Options : - // PCLZIP_OPT_BY_INDEX : - // PCLZIP_OPT_BY_NAME : - // PCLZIP_OPT_BY_EREG : - // PCLZIP_OPT_BY_PREG : - // Return Values : - // 0 on failure, - // The list of the files which are still present in the archive. - // (see PclZip::listContent() for list entry format) - // -------------------------------------------------------------------------------- - function delete() - { - $v_result=1; - - // ----- Reset the error handler - $this->privErrorReset(); - - // ----- Check archive - if (!$this->privCheckFormat()) { - return(0); - } + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : properties() + // Description : + // This method gives the properties of the archive. + // The properties are : + // nb : Number of files in the archive + // comment : Comment associated with the archive file + // status : not_exist, ok + // Parameters : + // None + // Return Values : + // 0 on failure, + // An array with the archive properties. + // -------------------------------------------------------------------------------- + public function properties() + { - // ----- Set default values - $v_options = array(); - - // ----- Look for variable options arguments - $v_size = func_num_args(); - - // ----- Look for arguments - if ($v_size > 0) { - // ----- Get the arguments - $v_arg_list = func_get_args(); - - // ----- Parse the options - $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, - array (PCLZIP_OPT_BY_NAME => 'optional', - PCLZIP_OPT_BY_EREG => 'optional', - PCLZIP_OPT_BY_PREG => 'optional', - PCLZIP_OPT_BY_INDEX => 'optional' )); - if ($v_result != 1) { - return 0; - } - } + // ----- Reset the error handler + $this->privErrorReset(); - // ----- Magic quotes trick - $this->privDisableMagicQuotes(); + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); - // ----- Call the delete fct - $v_list = array(); - if (($v_result = $this->privDeleteByRule($v_list, $v_options)) != 1) { - $this->privSwapBackMagicQuotes(); - unset($v_list); - return(0); - } + // ----- Check archive + if (!$this->privCheckFormat()) { + $this->privSwapBackMagicQuotes(); - // ----- Magic quotes trick - $this->privSwapBackMagicQuotes(); + return (0); + } - // ----- Return - return $v_list; - } - // -------------------------------------------------------------------------------- + // ----- Default properties + $v_prop = array(); + $v_prop['comment'] = ''; + $v_prop['nb'] = 0; + $v_prop['status'] = 'not_exist'; - // -------------------------------------------------------------------------------- - // Function : deleteByIndex() - // Description : - // ***** Deprecated ***** - // delete(PCLZIP_OPT_BY_INDEX, $p_index) should be prefered. - // -------------------------------------------------------------------------------- - function deleteByIndex($p_index) - { + // ----- Look if file exists + if (@is_file($this->zipname)) { + // ----- Open the zip file + if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0) { + $this->privSwapBackMagicQuotes(); - $p_list = $this->delete(PCLZIP_OPT_BY_INDEX, $p_index); + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \'' . $this->zipname . '\' in binary read mode'); - // ----- Return - return $p_list; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : properties() - // Description : - // This method gives the properties of the archive. - // The properties are : - // nb : Number of files in the archive - // comment : Comment associated with the archive file - // status : not_exist, ok - // Parameters : - // None - // Return Values : - // 0 on failure, - // An array with the archive properties. - // -------------------------------------------------------------------------------- - function properties() - { - - // ----- Reset the error handler - $this->privErrorReset(); - - // ----- Magic quotes trick - $this->privDisableMagicQuotes(); - - // ----- Check archive - if (!$this->privCheckFormat()) { - $this->privSwapBackMagicQuotes(); - return(0); - } + // ----- Return + return 0; + } - // ----- Default properties - $v_prop = array(); - $v_prop['comment'] = ''; - $v_prop['nb'] = 0; - $v_prop['status'] = 'not_exist'; + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + $this->privSwapBackMagicQuotes(); - // ----- Look if file exists - if (@is_file($this->zipname)) - { - // ----- Open the zip file - if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0) - { - $this->privSwapBackMagicQuotes(); + return 0; + } - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in binary read mode'); + // ----- Close the zip file + $this->privCloseFd(); - // ----- Return - return 0; - } + // ----- Set the user attributes + $v_prop['comment'] = $v_central_dir['comment']; + $v_prop['nb'] = $v_central_dir['entries']; + $v_prop['status'] = 'ok'; + } - // ----- Read the central directory informations - $v_central_dir = array(); - if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) - { + // ----- Magic quotes trick $this->privSwapBackMagicQuotes(); - return 0; - } - // ----- Close the zip file - $this->privCloseFd(); + // ----- Return + return $v_prop; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : duplicate() + // Description : + // This method creates an archive by copying the content of an other one. If + // the archive already exist, it is replaced by the new one without any warning. + // Parameters : + // $p_archive : The filename of a valid archive, or + // a valid PclZip object. + // Return Values : + // 1 on success. + // 0 or a negative value on error (error code). + // -------------------------------------------------------------------------------- + public function duplicate($p_archive) + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); - // ----- Set the user attributes - $v_prop['comment'] = $v_central_dir['comment']; - $v_prop['nb'] = $v_central_dir['entries']; - $v_prop['status'] = 'ok'; - } + // ----- Look if the $p_archive is a PclZip object + if ((is_object($p_archive)) && (get_class($p_archive) == 'pclzip')) { - // ----- Magic quotes trick - $this->privSwapBackMagicQuotes(); + // ----- Duplicate the archive + $v_result = $this->privDuplicate($p_archive->zipname); - // ----- Return - return $v_prop; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : duplicate() - // Description : - // This method creates an archive by copying the content of an other one. If - // the archive already exist, it is replaced by the new one without any warning. - // Parameters : - // $p_archive : The filename of a valid archive, or - // a valid PclZip object. - // Return Values : - // 1 on success. - // 0 or a negative value on error (error code). - // -------------------------------------------------------------------------------- - function duplicate($p_archive) - { - $v_result = 1; + // ----- Look if the $p_archive is a string (so a filename) + } elseif (is_string($p_archive)) { - // ----- Reset the error handler - $this->privErrorReset(); + // ----- Check that $p_archive is a valid zip file + // TBC : Should also check the archive format + if (!is_file($p_archive)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "No file with filename '" . $p_archive . "'"); + $v_result = PCLZIP_ERR_MISSING_FILE; + } else { + // ----- Duplicate the archive + $v_result = $this->privDuplicate($p_archive); + } - // ----- Look if the $p_archive is a PclZip object - if ((is_object($p_archive)) && (get_class($p_archive) == 'pclzip')) - { + // ----- Invalid variable + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add"); + $v_result = PCLZIP_ERR_INVALID_PARAMETER; + } - // ----- Duplicate the archive - $v_result = $this->privDuplicate($p_archive->zipname); + // ----- Return + return $v_result; } - - // ----- Look if the $p_archive is a string (so a filename) - else if (is_string($p_archive)) + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : merge() + // Description : + // This method merge the $p_archive_to_add archive at the end of the current + // one ($this). + // If the archive ($this) does not exist, the merge becomes a duplicate. + // If the $p_archive_to_add archive does not exist, the merge is a success. + // Parameters : + // $p_archive_to_add : It can be directly the filename of a valid zip archive, + // or a PclZip object archive. + // Return Values : + // 1 on success, + // 0 or negative values on error (see below). + // -------------------------------------------------------------------------------- + public function merge($p_archive_to_add) { + $v_result = 1; - // ----- Check that $p_archive is a valid zip file - // TBC : Should also check the archive format - if (!is_file($p_archive)) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "No file with filename '".$p_archive."'"); - $v_result = PCLZIP_ERR_MISSING_FILE; - } - else { - // ----- Duplicate the archive - $v_result = $this->privDuplicate($p_archive); - } - } + // ----- Reset the error handler + $this->privErrorReset(); - // ----- Invalid variable - else - { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add"); - $v_result = PCLZIP_ERR_INVALID_PARAMETER; - } + // ----- Check archive + if (!$this->privCheckFormat()) { + return (0); + } - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : merge() - // Description : - // This method merge the $p_archive_to_add archive at the end of the current - // one ($this). - // If the archive ($this) does not exist, the merge becomes a duplicate. - // If the $p_archive_to_add archive does not exist, the merge is a success. - // Parameters : - // $p_archive_to_add : It can be directly the filename of a valid zip archive, - // or a PclZip object archive. - // Return Values : - // 1 on success, - // 0 or negative values on error (see below). - // -------------------------------------------------------------------------------- - function merge($p_archive_to_add) - { - $v_result = 1; + // ----- Look if the $p_archive_to_add is a PclZip object + if ((is_object($p_archive_to_add)) && (get_class($p_archive_to_add) == 'pclzip')) { + + // ----- Merge the archive + $v_result = $this->privMerge($p_archive_to_add); + + // ----- Look if the $p_archive_to_add is a string (so a filename) + } elseif (is_string($p_archive_to_add)) { - // ----- Reset the error handler - $this->privErrorReset(); + // ----- Create a temporary archive + $v_object_archive = new PclZip($p_archive_to_add); - // ----- Check archive - if (!$this->privCheckFormat()) { - return(0); + // ----- Merge the archive + $v_result = $this->privMerge($v_object_archive); + + // ----- Invalid variable + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add"); + $v_result = PCLZIP_ERR_INVALID_PARAMETER; + } + + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- - // ----- Look if the $p_archive_to_add is a PclZip object - if ((is_object($p_archive_to_add)) && (get_class($p_archive_to_add) == 'pclzip')) + // -------------------------------------------------------------------------------- + // Function : errorCode() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function errorCode() { + if (PCLZIP_ERROR_EXTERNAL == 1) { + return (PclErrorCode()); + } - // ----- Merge the archive - $v_result = $this->privMerge($p_archive_to_add); + return ($this->error_code); } + // -------------------------------------------------------------------------------- - // ----- Look if the $p_archive_to_add is a string (so a filename) - else if (is_string($p_archive_to_add)) + // -------------------------------------------------------------------------------- + // Function : errorName() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function errorName($p_with_code = false) { + $v_name = array( + PCLZIP_ERR_NO_ERROR => 'PCLZIP_ERR_NO_ERROR', + PCLZIP_ERR_WRITE_OPEN_FAIL => 'PCLZIP_ERR_WRITE_OPEN_FAIL', + PCLZIP_ERR_READ_OPEN_FAIL => 'PCLZIP_ERR_READ_OPEN_FAIL', + PCLZIP_ERR_INVALID_PARAMETER => 'PCLZIP_ERR_INVALID_PARAMETER', + PCLZIP_ERR_MISSING_FILE => 'PCLZIP_ERR_MISSING_FILE', + PCLZIP_ERR_FILENAME_TOO_LONG => 'PCLZIP_ERR_FILENAME_TOO_LONG', + PCLZIP_ERR_INVALID_ZIP => 'PCLZIP_ERR_INVALID_ZIP', + PCLZIP_ERR_BAD_EXTRACTED_FILE => 'PCLZIP_ERR_BAD_EXTRACTED_FILE', + PCLZIP_ERR_DIR_CREATE_FAIL => 'PCLZIP_ERR_DIR_CREATE_FAIL', + PCLZIP_ERR_BAD_EXTENSION => 'PCLZIP_ERR_BAD_EXTENSION', + PCLZIP_ERR_BAD_FORMAT => 'PCLZIP_ERR_BAD_FORMAT', + PCLZIP_ERR_DELETE_FILE_FAIL => 'PCLZIP_ERR_DELETE_FILE_FAIL', + PCLZIP_ERR_RENAME_FILE_FAIL => 'PCLZIP_ERR_RENAME_FILE_FAIL', + PCLZIP_ERR_BAD_CHECKSUM => 'PCLZIP_ERR_BAD_CHECKSUM', + PCLZIP_ERR_INVALID_ARCHIVE_ZIP => 'PCLZIP_ERR_INVALID_ARCHIVE_ZIP', + PCLZIP_ERR_MISSING_OPTION_VALUE => 'PCLZIP_ERR_MISSING_OPTION_VALUE', + PCLZIP_ERR_INVALID_OPTION_VALUE => 'PCLZIP_ERR_INVALID_OPTION_VALUE', + PCLZIP_ERR_UNSUPPORTED_COMPRESSION => 'PCLZIP_ERR_UNSUPPORTED_COMPRESSION', + PCLZIP_ERR_UNSUPPORTED_ENCRYPTION => 'PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', + PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE => 'PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', + PCLZIP_ERR_DIRECTORY_RESTRICTION => 'PCLZIP_ERR_DIRECTORY_RESTRICTION' + ); + + if (isset($v_name[$this->error_code])) { + $v_value = $v_name[$this->error_code]; + } else { + $v_value = 'NoName'; + } - // ----- Create a temporary archive - $v_object_archive = new PclZip($p_archive_to_add); + if ($p_with_code) { + return ($v_value . ' (' . $this->error_code . ')'); + } - // ----- Merge the archive - $v_result = $this->privMerge($v_object_archive); + return ($v_value); } + // -------------------------------------------------------------------------------- - // ----- Invalid variable - else + // -------------------------------------------------------------------------------- + // Function : errorInfo() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function errorInfo($p_full = false) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add"); - $v_result = PCLZIP_ERR_INVALID_PARAMETER; - } + if (PCLZIP_ERROR_EXTERNAL == 1) { + return (PclErrorString()); + } - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- + if ($p_full) { + return ($this->errorName(true) . " : " . $this->error_string); + } + return ($this->error_string . " [code " . $this->error_code . "]"); + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // ***** UNDER THIS LINE ARE DEFINED PRIVATE INTERNAL FUNCTIONS ***** + // ***** ***** + // ***** THESES FUNCTIONS MUST NOT BE USED DIRECTLY ***** + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCheckFormat() + // Description : + // This method check that the archive exists and is a valid zip archive. + // Several level of check exists. (futur) + // Parameters : + // $p_level : Level of check. Default 0. + // 0 : Check the first bytes (magic codes) (default value)) + // 1 : 0 + Check the central directory (futur) + // 2 : 1 + Check each file header (futur) + // Return Values : + // true on success, + // false on error, the error code is set. + // -------------------------------------------------------------------------------- + public function privCheckFormat($p_level = 0) + { + $v_result = true; + // ----- Reset the file system cache + clearstatcache(); - // -------------------------------------------------------------------------------- - // Function : errorCode() - // Description : - // Parameters : - // -------------------------------------------------------------------------------- - function errorCode() - { - if (PCLZIP_ERROR_EXTERNAL == 1) { - return(PclErrorCode()); - } - else { - return($this->error_code); - } - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : errorName() - // Description : - // Parameters : - // -------------------------------------------------------------------------------- - function errorName($p_with_code=false) - { - $v_name = array ( PCLZIP_ERR_NO_ERROR => 'PCLZIP_ERR_NO_ERROR', - PCLZIP_ERR_WRITE_OPEN_FAIL => 'PCLZIP_ERR_WRITE_OPEN_FAIL', - PCLZIP_ERR_READ_OPEN_FAIL => 'PCLZIP_ERR_READ_OPEN_FAIL', - PCLZIP_ERR_INVALID_PARAMETER => 'PCLZIP_ERR_INVALID_PARAMETER', - PCLZIP_ERR_MISSING_FILE => 'PCLZIP_ERR_MISSING_FILE', - PCLZIP_ERR_FILENAME_TOO_LONG => 'PCLZIP_ERR_FILENAME_TOO_LONG', - PCLZIP_ERR_INVALID_ZIP => 'PCLZIP_ERR_INVALID_ZIP', - PCLZIP_ERR_BAD_EXTRACTED_FILE => 'PCLZIP_ERR_BAD_EXTRACTED_FILE', - PCLZIP_ERR_DIR_CREATE_FAIL => 'PCLZIP_ERR_DIR_CREATE_FAIL', - PCLZIP_ERR_BAD_EXTENSION => 'PCLZIP_ERR_BAD_EXTENSION', - PCLZIP_ERR_BAD_FORMAT => 'PCLZIP_ERR_BAD_FORMAT', - PCLZIP_ERR_DELETE_FILE_FAIL => 'PCLZIP_ERR_DELETE_FILE_FAIL', - PCLZIP_ERR_RENAME_FILE_FAIL => 'PCLZIP_ERR_RENAME_FILE_FAIL', - PCLZIP_ERR_BAD_CHECKSUM => 'PCLZIP_ERR_BAD_CHECKSUM', - PCLZIP_ERR_INVALID_ARCHIVE_ZIP => 'PCLZIP_ERR_INVALID_ARCHIVE_ZIP', - PCLZIP_ERR_MISSING_OPTION_VALUE => 'PCLZIP_ERR_MISSING_OPTION_VALUE', - PCLZIP_ERR_INVALID_OPTION_VALUE => 'PCLZIP_ERR_INVALID_OPTION_VALUE', - PCLZIP_ERR_UNSUPPORTED_COMPRESSION => 'PCLZIP_ERR_UNSUPPORTED_COMPRESSION', - PCLZIP_ERR_UNSUPPORTED_ENCRYPTION => 'PCLZIP_ERR_UNSUPPORTED_ENCRYPTION' - ,PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE => 'PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE' - ,PCLZIP_ERR_DIRECTORY_RESTRICTION => 'PCLZIP_ERR_DIRECTORY_RESTRICTION' - ); - - if (isset($v_name[$this->error_code])) { - $v_value = $v_name[$this->error_code]; - } - else { - $v_value = 'NoName'; - } + // ----- Reset the error handler + $this->privErrorReset(); - if ($p_with_code) { - return($v_value.' ('.$this->error_code.')'); - } - else { - return($v_value); - } - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : errorInfo() - // Description : - // Parameters : - // -------------------------------------------------------------------------------- - function errorInfo($p_full=false) - { - if (PCLZIP_ERROR_EXTERNAL == 1) { - return(PclErrorString()); - } - else { - if ($p_full) { - return($this->errorName(true)." : ".$this->error_string); - } - else { - return($this->error_string." [code ".$this->error_code."]"); - } - } - } - // -------------------------------------------------------------------------------- + // ----- Look if the file exits + if (!is_file($this->zipname)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "Missing archive file '" . $this->zipname . "'"); + return (false); + } -// -------------------------------------------------------------------------------- -// ***** UNDER THIS LINE ARE DEFINED PRIVATE INTERNAL FUNCTIONS ***** -// ***** ***** -// ***** THESES FUNCTIONS MUST NOT BE USED DIRECTLY ***** -// -------------------------------------------------------------------------------- + // ----- Check that the file is readeable + if (!is_readable($this->zipname)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to read archive '" . $this->zipname . "'"); + return (false); + } + // ----- Check the magic code + // TBC - // -------------------------------------------------------------------------------- - // Function : privCheckFormat() - // Description : - // This method check that the archive exists and is a valid zip archive. - // Several level of check exists. (futur) - // Parameters : - // $p_level : Level of check. Default 0. - // 0 : Check the first bytes (magic codes) (default value)) - // 1 : 0 + Check the central directory (futur) - // 2 : 1 + Check each file header (futur) - // Return Values : - // true on success, - // false on error, the error code is set. - // -------------------------------------------------------------------------------- - function privCheckFormat($p_level=0) - { - $v_result = true; - - // ----- Reset the file system cache - clearstatcache(); - - // ----- Reset the error handler - $this->privErrorReset(); - - // ----- Look if the file exits - if (!is_file($this->zipname)) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "Missing archive file '".$this->zipname."'"); - return(false); - } + // ----- Check the central header + // TBC - // ----- Check that the file is readeable - if (!is_readable($this->zipname)) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to read archive '".$this->zipname."'"); - return(false); + // ----- Check each file header + // TBC + + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privParseOptions() + // Description : + // This internal methods reads the variable list of arguments ($p_options_list, + // $p_size) and generate an array with the options and values ($v_result_list). + // $v_requested_options contains the options that can be present and those that + // must be present. + // $v_requested_options is an array, with the option value as key, and 'optional', + // or 'mandatory' as value. + // Parameters : + // See above. + // Return Values : + // 1 on success. + // 0 on failure. + // -------------------------------------------------------------------------------- + public function privParseOptions(&$p_options_list, $p_size, &$v_result_list, $v_requested_options = false) + { + $v_result = 1; - // ----- Check the magic code - // TBC + // ----- Read the options + $i = 0; + while ($i < $p_size) { - // ----- Check the central header - // TBC + // ----- Check if the option is supported + if (!isset($v_requested_options[$p_options_list[$i]])) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid optional parameter '" . $p_options_list[$i] . "' for this method"); - // ----- Check each file header - // TBC + // ----- Return + return PclZip::errorCode(); + } - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privParseOptions() - // Description : - // This internal methods reads the variable list of arguments ($p_options_list, - // $p_size) and generate an array with the options and values ($v_result_list). - // $v_requested_options contains the options that can be present and those that - // must be present. - // $v_requested_options is an array, with the option value as key, and 'optional', - // or 'mandatory' as value. - // Parameters : - // See above. - // Return Values : - // 1 on success. - // 0 on failure. - // -------------------------------------------------------------------------------- - function privParseOptions(&$p_options_list, $p_size, &$v_result_list, $v_requested_options=false) - { - $v_result=1; - - // ----- Read the options - $i=0; - while ($i<$p_size) { - - // ----- Check if the option is supported - if (!isset($v_requested_options[$p_options_list[$i]])) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid optional parameter '".$p_options_list[$i]."' for this method"); + // ----- Look for next option + switch ($p_options_list[$i]) { + // ----- Look for options that request a path value + case PCLZIP_OPT_PATH: + case PCLZIP_OPT_REMOVE_PATH: + case PCLZIP_OPT_ADD_PATH: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i + 1], false); + $i++; + break; + + case PCLZIP_OPT_TEMP_FILE_THRESHOLD: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + return PclZip::errorCode(); + } + + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'"); + + return PclZip::errorCode(); + } + + // ----- Check the value + $v_value = $p_options_list[$i + 1]; + if ((!is_integer($v_value)) || ($v_value < 0)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Integer expected for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + return PclZip::errorCode(); + } + + // ----- Get the value (and convert it in bytes) + $v_result_list[$p_options_list[$i]] = $v_value * 1048576; + $i++; + break; + + case PCLZIP_OPT_TEMP_FILE_ON: + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'"); + + return PclZip::errorCode(); + } + + $v_result_list[$p_options_list[$i]] = true; + break; + + case PCLZIP_OPT_TEMP_FILE_OFF: + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_ON])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_ON'"); + + return PclZip::errorCode(); + } + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_THRESHOLD'"); + + return PclZip::errorCode(); + } + + $v_result_list[$p_options_list[$i]] = true; + break; + + case PCLZIP_OPT_EXTRACT_DIR_RESTRICTION: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i + 1]) && ($p_options_list[$i + 1] != '')) { + $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i + 1], false); + $i++; + } else { + } + break; + + // ----- Look for options that request an array of string for value + case PCLZIP_OPT_BY_NAME: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i + 1])) { + $v_result_list[$p_options_list[$i]][0] = $p_options_list[$i + 1]; + } elseif (is_array($p_options_list[$i + 1])) { + $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1]; + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + $i++; + break; + + // ----- Look for options that request an EREG or PREG expression + case PCLZIP_OPT_BY_EREG: + $p_options_list[$i] = PCLZIP_OPT_BY_PREG; + // ereg() is deprecated starting with PHP 5.3. Move PCLZIP_OPT_BY_EREG + // to PCLZIP_OPT_BY_PREG + case PCLZIP_OPT_BY_PREG: + //case PCLZIP_OPT_CRYPT : + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i + 1])) { + $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1]; + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + $i++; + break; + + // ----- Look for options that takes a string + case PCLZIP_OPT_COMMENT: + case PCLZIP_OPT_ADD_COMMENT: + case PCLZIP_OPT_PREPEND_COMMENT: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i + 1])) { + $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1]; + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + $i++; + break; + + // ----- Look for options that request an array of index + case PCLZIP_OPT_BY_INDEX: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_work_list = array(); + if (is_string($p_options_list[$i + 1])) { + + // ----- Remove spaces + $p_options_list[$i + 1] = str_replace(' ', '', $p_options_list[$i + 1]); + + // ----- Parse items + $v_work_list = explode(",", $p_options_list[$i + 1]); + } elseif (is_integer($p_options_list[$i + 1])) { + $v_work_list[0] = $p_options_list[$i + 1] . '-' . $p_options_list[$i + 1]; + } elseif (is_array($p_options_list[$i + 1])) { + $v_work_list = $p_options_list[$i + 1]; + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Value must be integer, string or array for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Reduce the index list + // each index item in the list must be a couple with a start and + // an end value : [0,3], [5-5], [8-10], ... + // ----- Check the format of each item + $v_sort_flag = false; + $v_sort_value = 0; + for ($j = 0; $j < sizeof($v_work_list); $j++) { + // ----- Explode the item + $v_item_list = explode("-", $v_work_list[$j]); + $v_size_item_list = sizeof($v_item_list); + + // ----- TBC : Here we might check that each item is a + // real integer ... + + // ----- Look for single value + if ($v_size_item_list == 1) { + // ----- Set the option value + $v_result_list[$p_options_list[$i]][$j]['start'] = $v_item_list[0]; + $v_result_list[$p_options_list[$i]][$j]['end'] = $v_item_list[0]; + } elseif ($v_size_item_list == 2) { + // ----- Set the option value + $v_result_list[$p_options_list[$i]][$j]['start'] = $v_item_list[0]; + $v_result_list[$p_options_list[$i]][$j]['end'] = $v_item_list[1]; + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Too many values in index range for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for list sort + if ($v_result_list[$p_options_list[$i]][$j]['start'] < $v_sort_value) { + $v_sort_flag = true; + + // ----- TBC : An automatic sort should be writen ... + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Invalid order of index range for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + $v_sort_value = $v_result_list[$p_options_list[$i]][$j]['start']; + } + + // ----- Sort the items + if ($v_sort_flag) { + // TBC : To Be Completed + } + + // ----- Next option + $i++; + break; + + // ----- Look for options that request no value + case PCLZIP_OPT_REMOVE_ALL_PATH: + case PCLZIP_OPT_EXTRACT_AS_STRING: + case PCLZIP_OPT_NO_COMPRESSION: + case PCLZIP_OPT_EXTRACT_IN_OUTPUT: + case PCLZIP_OPT_REPLACE_NEWER: + case PCLZIP_OPT_STOP_ON_ERROR: + $v_result_list[$p_options_list[$i]] = true; + break; + + // ----- Look for options that request an octal value + case PCLZIP_OPT_SET_CHMOD: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1]; + $i++; + break; + + // ----- Look for options that request a call-back + case PCLZIP_CB_PRE_EXTRACT: + case PCLZIP_CB_POST_EXTRACT: + case PCLZIP_CB_PRE_ADD: + case PCLZIP_CB_POST_ADD: + /* for futur use + case PCLZIP_CB_PRE_DELETE : + case PCLZIP_CB_POST_DELETE : + case PCLZIP_CB_PRE_LIST : + case PCLZIP_CB_POST_LIST : + */ + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_function_name = $p_options_list[$i + 1]; + + // ----- Check that the value is a valid existing function + if (!function_exists($v_function_name)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Function '" . $v_function_name . "()' is not an existing function for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Set the attribute + $v_result_list[$p_options_list[$i]] = $v_function_name; + $i++; + break; + + default: + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Unknown parameter '" . $p_options_list[$i] . "'"); + + // ----- Return + return PclZip::errorCode(); + } - // ----- Return - return PclZip::errorCode(); - } - - // ----- Look for next option - switch ($p_options_list[$i]) { - // ----- Look for options that request a path value - case PCLZIP_OPT_PATH : - case PCLZIP_OPT_REMOVE_PATH : - case PCLZIP_OPT_ADD_PATH : - // ----- Check the number of parameters - if (($i+1) >= $p_size) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + // ----- Next options + $i++; + } - // ----- Return - return PclZip::errorCode(); - } + // ----- Look for mandatory options + if ($v_requested_options !== false) { + for ($key = reset($v_requested_options); $key = key($v_requested_options); $key = next($v_requested_options)) { + // ----- Look for mandatory option + if ($v_requested_options[$key] == 'mandatory') { + // ----- Look if present + if (!isset($v_result_list[$key])) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter " . PclZipUtilOptionText($key) . "(" . $key . ")"); + + // ----- Return + return PclZip::errorCode(); + } + } + } + } - // ----- Get the value - $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i+1], FALSE); - $i++; - break; + // ----- Look for default values + if (!isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) { - case PCLZIP_OPT_TEMP_FILE_THRESHOLD : - // ----- Check the number of parameters - if (($i+1) >= $p_size) { - PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); - return PclZip::errorCode(); - } + } - // ----- Check for incompatible options - if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'"); - return PclZip::errorCode(); - } + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- - // ----- Check the value - $v_value = $p_options_list[$i+1]; - if ((!is_integer($v_value)) || ($v_value<0)) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Integer expected for option '".PclZipUtilOptionText($p_options_list[$i])."'"); - return PclZip::errorCode(); - } + // -------------------------------------------------------------------------------- + // Function : privOptionDefaultThreshold() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privOptionDefaultThreshold(&$p_options) + { + $v_result = 1; - // ----- Get the value (and convert it in bytes) - $v_result_list[$p_options_list[$i]] = $v_value*1048576; - $i++; - break; + if (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) || isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) { + return $v_result; + } - case PCLZIP_OPT_TEMP_FILE_ON : - // ----- Check for incompatible options - if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'"); - return PclZip::errorCode(); - } + // ----- Get 'memory_limit' configuration value + $v_memory_limit = ini_get('memory_limit'); + $v_memory_limit = trim($v_memory_limit); + $last = strtolower(substr($v_memory_limit, -1)); + $v_memory_limit = preg_replace('/[^0-9,.]/', '', $v_memory_limit); - $v_result_list[$p_options_list[$i]] = true; - break; + if ($last == 'g') { + //$v_memory_limit = $v_memory_limit*1024*1024*1024; + $v_memory_limit = $v_memory_limit * 1073741824; + } + if ($last == 'm') { + //$v_memory_limit = $v_memory_limit*1024*1024; + $v_memory_limit = $v_memory_limit * 1048576; + } + if ($last == 'k') { + $v_memory_limit = $v_memory_limit * 1024; + } - case PCLZIP_OPT_TEMP_FILE_OFF : - // ----- Check for incompatible options - if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_ON])) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_ON'"); - return PclZip::errorCode(); - } - // ----- Check for incompatible options - if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_THRESHOLD'"); - return PclZip::errorCode(); - } + $p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] = floor($v_memory_limit * PCLZIP_TEMPORARY_FILE_RATIO); - $v_result_list[$p_options_list[$i]] = true; - break; + // ----- Sanity check : No threshold if value lower than 1M + if ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] < 1048576) { + unset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]); + } - case PCLZIP_OPT_EXTRACT_DIR_RESTRICTION : - // ----- Check the number of parameters - if (($i+1) >= $p_size) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privFileDescrParseAtt() + // Description : + // Parameters : + // Return Values : + // 1 on success. + // 0 on failure. + // -------------------------------------------------------------------------------- + public function privFileDescrParseAtt(&$p_file_list, &$p_filedescr, $v_options, $v_requested_options = false) + { + $v_result = 1; - // ----- Return - return PclZip::errorCode(); - } + // ----- For each file in the list check the attributes + foreach ($p_file_list as $v_key => $v_value) { - // ----- Get the value - if ( is_string($p_options_list[$i+1]) - && ($p_options_list[$i+1] != '')) { - $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i+1], FALSE); - $i++; - } - else { - } - break; - - // ----- Look for options that request an array of string for value - case PCLZIP_OPT_BY_NAME : - // ----- Check the number of parameters - if (($i+1) >= $p_size) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + // ----- Check if the option is supported + if (!isset($v_requested_options[$v_key])) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file attribute '" . $v_key . "' for this file"); - // ----- Return - return PclZip::errorCode(); - } - - // ----- Get the value - if (is_string($p_options_list[$i+1])) { - $v_result_list[$p_options_list[$i]][0] = $p_options_list[$i+1]; - } - else if (is_array($p_options_list[$i+1])) { - $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1]; - } - else { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + // ----- Return + return PclZip::errorCode(); + } - // ----- Return - return PclZip::errorCode(); - } - $i++; - break; - - // ----- Look for options that request an EREG or PREG expression - case PCLZIP_OPT_BY_EREG : - // ereg() is deprecated starting with PHP 5.3. Move PCLZIP_OPT_BY_EREG - // to PCLZIP_OPT_BY_PREG - $p_options_list[$i] = PCLZIP_OPT_BY_PREG; - case PCLZIP_OPT_BY_PREG : - //case PCLZIP_OPT_CRYPT : - // ----- Check the number of parameters - if (($i+1) >= $p_size) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + // ----- Look for attribute + switch ($v_key) { + case PCLZIP_ATT_FILE_NAME: + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'"); - // ----- Return - return PclZip::errorCode(); - } + return PclZip::errorCode(); + } - // ----- Get the value - if (is_string($p_options_list[$i+1])) { - $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1]; - } - else { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + $p_filedescr['filename'] = PclZipUtilPathReduction($v_value); - // ----- Return - return PclZip::errorCode(); - } - $i++; - break; - - // ----- Look for options that takes a string - case PCLZIP_OPT_COMMENT : - case PCLZIP_OPT_ADD_COMMENT : - case PCLZIP_OPT_PREPEND_COMMENT : - // ----- Check the number of parameters - if (($i+1) >= $p_size) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, - "Missing parameter value for option '" - .PclZipUtilOptionText($p_options_list[$i]) - ."'"); + if ($p_filedescr['filename'] == '') { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty filename for attribute '" . PclZipUtilOptionText($v_key) . "'"); - // ----- Return - return PclZip::errorCode(); - } + return PclZip::errorCode(); + } - // ----- Get the value - if (is_string($p_options_list[$i+1])) { - $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1]; - } - else { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, - "Wrong parameter value for option '" - .PclZipUtilOptionText($p_options_list[$i]) - ."'"); + break; - // ----- Return - return PclZip::errorCode(); - } - $i++; - break; - - // ----- Look for options that request an array of index - case PCLZIP_OPT_BY_INDEX : - // ----- Check the number of parameters - if (($i+1) >= $p_size) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + case PCLZIP_ATT_FILE_NEW_SHORT_NAME: + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'"); - // ----- Return - return PclZip::errorCode(); - } - - // ----- Get the value - $v_work_list = array(); - if (is_string($p_options_list[$i+1])) { - - // ----- Remove spaces - $p_options_list[$i+1] = strtr($p_options_list[$i+1], ' ', ''); - - // ----- Parse items - $v_work_list = explode(",", $p_options_list[$i+1]); - } - else if (is_integer($p_options_list[$i+1])) { - $v_work_list[0] = $p_options_list[$i+1].'-'.$p_options_list[$i+1]; - } - else if (is_array($p_options_list[$i+1])) { - $v_work_list = $p_options_list[$i+1]; - } - else { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Value must be integer, string or array for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + return PclZip::errorCode(); + } - // ----- Return - return PclZip::errorCode(); - } - - // ----- Reduce the index list - // each index item in the list must be a couple with a start and - // an end value : [0,3], [5-5], [8-10], ... - // ----- Check the format of each item - $v_sort_flag=false; - $v_sort_value=0; - for ($j=0; $j= $p_size) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + $p_filedescr['new_short_name'] = PclZipUtilPathReduction($v_value); - // ----- Return - return PclZip::errorCode(); - } - - // ----- Get the value - $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1]; - $i++; - break; - - // ----- Look for options that request a call-back - case PCLZIP_CB_PRE_EXTRACT : - case PCLZIP_CB_POST_EXTRACT : - case PCLZIP_CB_PRE_ADD : - case PCLZIP_CB_POST_ADD : - /* for futur use - case PCLZIP_CB_PRE_DELETE : - case PCLZIP_CB_POST_DELETE : - case PCLZIP_CB_PRE_LIST : - case PCLZIP_CB_POST_LIST : - */ - // ----- Check the number of parameters - if (($i+1) >= $p_size) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + if ($p_filedescr['new_short_name'] == '') { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty short filename for attribute '" . PclZipUtilOptionText($v_key) . "'"); - // ----- Return - return PclZip::errorCode(); - } + return PclZip::errorCode(); + } + break; - // ----- Get the value - $v_function_name = $p_options_list[$i+1]; + case PCLZIP_ATT_FILE_NEW_FULL_NAME: + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'"); - // ----- Check that the value is a valid existing function - if (!function_exists($v_function_name)) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Function '".$v_function_name."()' is not an existing function for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + return PclZip::errorCode(); + } - // ----- Return - return PclZip::errorCode(); - } + $p_filedescr['new_full_name'] = PclZipUtilPathReduction($v_value); - // ----- Set the attribute - $v_result_list[$p_options_list[$i]] = $v_function_name; - $i++; - break; + if ($p_filedescr['new_full_name'] == '') { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty full filename for attribute '" . PclZipUtilOptionText($v_key) . "'"); - default : - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, - "Unknown parameter '" - .$p_options_list[$i]."'"); + return PclZip::errorCode(); + } + break; - // ----- Return - return PclZip::errorCode(); - } + // ----- Look for options that takes a string + case PCLZIP_ATT_FILE_COMMENT: + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'"); - // ----- Next options - $i++; - } + return PclZip::errorCode(); + } - // ----- Look for mandatory options - if ($v_requested_options !== false) { - for ($key=reset($v_requested_options); $key=key($v_requested_options); $key=next($v_requested_options)) { - // ----- Look for mandatory option - if ($v_requested_options[$key] == 'mandatory') { - // ----- Look if present - if (!isset($v_result_list[$key])) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter ".PclZipUtilOptionText($key)."(".$key.")"); + $p_filedescr['comment'] = $v_value; + break; - // ----- Return - return PclZip::errorCode(); - } - } - } - } + case PCLZIP_ATT_FILE_MTIME: + if (!is_integer($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". Integer expected for attribute '" . PclZipUtilOptionText($v_key) . "'"); - // ----- Look for default values - if (!isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) { + return PclZip::errorCode(); + } - } + $p_filedescr['mtime'] = $v_value; + break; - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privOptionDefaultThreshold() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privOptionDefaultThreshold(&$p_options) - { - $v_result=1; - - if (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) - || isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) { - return $v_result; - } + case PCLZIP_ATT_FILE_CONTENT: + $p_filedescr['content'] = $v_value; + break; - // ----- Get 'memory_limit' configuration value - $v_memory_limit = ini_get('memory_limit'); - $v_memory_limit = trim($v_memory_limit); - $last = strtolower(substr($v_memory_limit, -1)); + default: + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Unknown parameter '" . $v_key . "'"); - if($last == 'g') - //$v_memory_limit = $v_memory_limit*1024*1024*1024; - $v_memory_limit = $v_memory_limit*1073741824; - if($last == 'm') - //$v_memory_limit = $v_memory_limit*1024*1024; - $v_memory_limit = $v_memory_limit*1048576; - if($last == 'k') - $v_memory_limit = $v_memory_limit*1024; + // ----- Return + return PclZip::errorCode(); + } - $p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] = floor($v_memory_limit*PCLZIP_TEMPORARY_FILE_RATIO); + // ----- Look for mandatory options + if ($v_requested_options !== false) { + for ($key = reset($v_requested_options); $key = key($v_requested_options); $key = next($v_requested_options)) { + // ----- Look for mandatory option + if ($v_requested_options[$key] == 'mandatory') { + // ----- Look if present + if (!isset($p_file_list[$key])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter " . PclZipUtilOptionText($key) . "(" . $key . ")"); + + return PclZip::errorCode(); + } + } + } + } + // end foreach + } - // ----- Sanity check : No threshold if value lower than 1M - if ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] < 1048576) { - unset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]); + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privFileDescrExpand() + // Description : + // This method look for each item of the list to see if its a file, a folder + // or a string to be added as file. For any other type of files (link, other) + // just ignore the item. + // Then prepare the information that will be stored for that file. + // When its a folder, expand the folder with all the files that are in that + // folder (recursively). + // Parameters : + // Return Values : + // 1 on success. + // 0 on failure. + // -------------------------------------------------------------------------------- + public function privFileDescrExpand(&$p_filedescr_list, &$p_options) + { + $v_result = 1; - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privFileDescrParseAtt() - // Description : - // Parameters : - // Return Values : - // 1 on success. - // 0 on failure. - // -------------------------------------------------------------------------------- - function privFileDescrParseAtt(&$p_file_list, &$p_filedescr, $v_options, $v_requested_options=false) - { - $v_result=1; - - // ----- For each file in the list check the attributes - foreach ($p_file_list as $v_key => $v_value) { - - // ----- Check if the option is supported - if (!isset($v_requested_options[$v_key])) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file attribute '".$v_key."' for this file"); + // ----- Create a result list + $v_result_list = array(); + + // ----- Look each entry + for ($i = 0; $i < sizeof($p_filedescr_list); $i++) { + + // ----- Get filedescr + $v_descr = $p_filedescr_list[$i]; + + // ----- Reduce the filename + $v_descr['filename'] = PclZipUtilTranslateWinPath($v_descr['filename'], false); + $v_descr['filename'] = PclZipUtilPathReduction($v_descr['filename']); + + // ----- Look for real file or folder + if (file_exists($v_descr['filename'])) { + if (@is_file($v_descr['filename'])) { + $v_descr['type'] = 'file'; + } elseif (@is_dir($v_descr['filename'])) { + $v_descr['type'] = 'folder'; + } elseif (@is_link($v_descr['filename'])) { + // skip + continue; + } else { + // skip + continue; + } + + // ----- Look for string added as file + } elseif (isset($v_descr['content'])) { + $v_descr['type'] = 'virtual_file'; + + // ----- Missing file + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "File '" . $v_descr['filename'] . "' does not exist"); - // ----- Return - return PclZip::errorCode(); - } - - // ----- Look for attribute - switch ($v_key) { - case PCLZIP_ATT_FILE_NAME : - if (!is_string($v_value)) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'"); - return PclZip::errorCode(); - } + // ----- Return + return PclZip::errorCode(); + } - $p_filedescr['filename'] = PclZipUtilPathReduction($v_value); + // ----- Calculate the stored filename + $this->privCalculateStoredFilename($v_descr, $p_options); + + // ----- Add the descriptor in result list + $v_result_list[sizeof($v_result_list)] = $v_descr; + + // ----- Look for folder + if ($v_descr['type'] == 'folder') { + // ----- List of items in folder + $v_dirlist_descr = array(); + $v_dirlist_nb = 0; + if ($v_folder_handler = @opendir($v_descr['filename'])) { + while (($v_item_handler = @readdir($v_folder_handler)) !== false) { + + // ----- Skip '.' and '..' + if (($v_item_handler == '.') || ($v_item_handler == '..')) { + continue; + } + + // ----- Compose the full filename + $v_dirlist_descr[$v_dirlist_nb]['filename'] = $v_descr['filename'] . '/' . $v_item_handler; + + // ----- Look for different stored filename + // Because the name of the folder was changed, the name of the + // files/sub-folders also change + if (($v_descr['stored_filename'] != $v_descr['filename']) && (!isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))) { + if ($v_descr['stored_filename'] != '') { + $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_descr['stored_filename'] . '/' . $v_item_handler; + } else { + $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_item_handler; + } + } + + $v_dirlist_nb++; + } + + @closedir($v_folder_handler); + } else { + // TBC : unable to open folder in read mode + } + + // ----- Expand each element of the list + if ($v_dirlist_nb != 0) { + // ----- Expand + if (($v_result = $this->privFileDescrExpand($v_dirlist_descr, $p_options)) != 1) { + return $v_result; + } + + // ----- Concat the resulting list + $v_result_list = array_merge($v_result_list, $v_dirlist_descr); + } else { + } + + // ----- Free local array + unset($v_dirlist_descr); + } + } - if ($p_filedescr['filename'] == '') { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty filename for attribute '".PclZipUtilOptionText($v_key)."'"); - return PclZip::errorCode(); - } + // ----- Get the result list + $p_filedescr_list = $v_result_list; - break; + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- - case PCLZIP_ATT_FILE_NEW_SHORT_NAME : - if (!is_string($v_value)) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'"); - return PclZip::errorCode(); - } + // -------------------------------------------------------------------------------- + // Function : privCreate() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privCreate($p_filedescr_list, &$p_result_list, &$p_options) + { + $v_result = 1; + $v_list_detail = array(); - $p_filedescr['new_short_name'] = PclZipUtilPathReduction($v_value); + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); - if ($p_filedescr['new_short_name'] == '') { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty short filename for attribute '".PclZipUtilOptionText($v_key)."'"); - return PclZip::errorCode(); - } - break; + // ----- Open the file in write mode + if (($v_result = $this->privOpenFd('wb')) != 1) { + // ----- Return + return $v_result; + } - case PCLZIP_ATT_FILE_NEW_FULL_NAME : - if (!is_string($v_value)) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'"); - return PclZip::errorCode(); - } + // ----- Add the list of files + $v_result = $this->privAddList($p_filedescr_list, $p_result_list, $p_options); - $p_filedescr['new_full_name'] = PclZipUtilPathReduction($v_value); + // ----- Close + $this->privCloseFd(); - if ($p_filedescr['new_full_name'] == '') { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty full filename for attribute '".PclZipUtilOptionText($v_key)."'"); - return PclZip::errorCode(); - } - break; + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); - // ----- Look for options that takes a string - case PCLZIP_ATT_FILE_COMMENT : - if (!is_string($v_value)) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'"); - return PclZip::errorCode(); - } + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- - $p_filedescr['comment'] = $v_value; - break; + // -------------------------------------------------------------------------------- + // Function : privAdd() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privAdd($p_filedescr_list, &$p_result_list, &$p_options) + { + $v_result = 1; + $v_list_detail = array(); - case PCLZIP_ATT_FILE_MTIME : - if (!is_integer($v_value)) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". Integer expected for attribute '".PclZipUtilOptionText($v_key)."'"); - return PclZip::errorCode(); - } - - $p_filedescr['mtime'] = $v_value; - break; - - case PCLZIP_ATT_FILE_CONTENT : - $p_filedescr['content'] = $v_value; - break; - - default : - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, - "Unknown parameter '".$v_key."'"); - - // ----- Return - return PclZip::errorCode(); - } - - // ----- Look for mandatory options - if ($v_requested_options !== false) { - for ($key=reset($v_requested_options); $key=key($v_requested_options); $key=next($v_requested_options)) { - // ----- Look for mandatory option - if ($v_requested_options[$key] == 'mandatory') { - // ----- Look if present - if (!isset($p_file_list[$key])) { - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter ".PclZipUtilOptionText($key)."(".$key.")"); - return PclZip::errorCode(); - } - } - } - } + // ----- Look if the archive exists or is empty + if ((!is_file($this->zipname)) || (filesize($this->zipname) == 0)) { - // end foreach - } + // ----- Do a create + $v_result = $this->privCreate($p_filedescr_list, $p_result_list, $p_options); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privFileDescrExpand() - // Description : - // This method look for each item of the list to see if its a file, a folder - // or a string to be added as file. For any other type of files (link, other) - // just ignore the item. - // Then prepare the information that will be stored for that file. - // When its a folder, expand the folder with all the files that are in that - // folder (recursively). - // Parameters : - // Return Values : - // 1 on success. - // 0 on failure. - // -------------------------------------------------------------------------------- - function privFileDescrExpand(&$p_filedescr_list, &$p_options) - { - $v_result=1; - - // ----- Create a result list - $v_result_list = array(); - - // ----- Look each entry - for ($i=0; $iprivDisableMagicQuotes(); - // ----- Return - return PclZip::errorCode(); - } + // ----- Open the zip file + if (($v_result = $this->privOpenFd('rb')) != 1) { + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); - // ----- Calculate the stored filename - $this->privCalculateStoredFilename($v_descr, $p_options); + // ----- Return + return $v_result; + } - // ----- Add the descriptor in result list - $v_result_list[sizeof($v_result_list)] = $v_descr; + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); - // ----- Look for folder - if ($v_descr['type'] == 'folder') { - // ----- List of items in folder - $v_dirlist_descr = array(); - $v_dirlist_nb = 0; - if ($v_folder_handler = @opendir($v_descr['filename'])) { - while (($v_item_handler = @readdir($v_folder_handler)) !== false) { + return $v_result; + } - // ----- Skip '.' and '..' - if (($v_item_handler == '.') || ($v_item_handler == '..')) { - continue; - } + // ----- Go to beginning of File + @rewind($this->zip_fd); - // ----- Compose the full filename - $v_dirlist_descr[$v_dirlist_nb]['filename'] = $v_descr['filename'].'/'.$v_item_handler; - - // ----- Look for different stored filename - // Because the name of the folder was changed, the name of the - // files/sub-folders also change - if (($v_descr['stored_filename'] != $v_descr['filename']) - && (!isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))) { - if ($v_descr['stored_filename'] != '') { - $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_descr['stored_filename'].'/'.$v_item_handler; - } - else { - $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_item_handler; - } - } + // ----- Creates a temporay file + $v_zip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.tmp'; + + // ----- Open the temporary file in write mode + if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); - $v_dirlist_nb++; - } + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_zip_temp_name . '\' in binary write mode'); - @closedir($v_folder_handler); + // ----- Return + return PclZip::errorCode(); } - else { - // TBC : unable to open folder in read mode + + // ----- Copy the files from the archive to the temporary file + // TBC : Here I should better append the file and go back to erase the central dir + $v_size = $v_central_dir['offset']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($this->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; } - // ----- Expand each element of the list - if ($v_dirlist_nb != 0) { - // ----- Expand - if (($v_result = $this->privFileDescrExpand($v_dirlist_descr, $p_options)) != 1) { + // ----- Swap the file descriptor + // Here is a trick : I swap the temporary fd with the zip fd, in order to use + // the following methods on the temporary fil and not the real archive + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; + + // ----- Add the files + $v_header_list = array(); + if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1) { + fclose($v_zip_temp_fd); + $this->privCloseFd(); + @unlink($v_zip_temp_name); + $this->privSwapBackMagicQuotes(); + + // ----- Return return $v_result; - } + } + + // ----- Store the offset of the central dir + $v_offset = @ftell($this->zip_fd); - // ----- Concat the resulting list - $v_result_list = array_merge($v_result_list, $v_dirlist_descr); + // ----- Copy the block of file headers from the old archive + $v_size = $v_central_dir['size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($v_zip_temp_fd, $v_read_size); + @fwrite($this->zip_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; } - else { + + // ----- Create the Central Dir files header + for ($i = 0, $v_count = 0; $i < sizeof($v_header_list); $i++) { + // ----- Create the file header + if ($v_header_list[$i]['status'] == 'ok') { + if (($v_result = $this->privWriteCentralFileHeader($v_header_list[$i])) != 1) { + fclose($v_zip_temp_fd); + $this->privCloseFd(); + @unlink($v_zip_temp_name); + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + $v_count++; + } + + // ----- Transform the header to a 'usable' info + $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); } - // ----- Free local array - unset($v_dirlist_descr); - } - } + // ----- Zip file comment + $v_comment = $v_central_dir['comment']; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } + if (isset($p_options[PCLZIP_OPT_ADD_COMMENT])) { + $v_comment = $v_comment . $p_options[PCLZIP_OPT_ADD_COMMENT]; + } + if (isset($p_options[PCLZIP_OPT_PREPEND_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_PREPEND_COMMENT] . $v_comment; + } - // ----- Get the result list - $p_filedescr_list = $v_result_list; + // ----- Calculate the size of the central header + $v_size = @ftell($this->zip_fd) - $v_offset; - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privCreate() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privCreate($p_filedescr_list, &$p_result_list, &$p_options) - { - $v_result=1; - $v_list_detail = array(); - - // ----- Magic quotes trick - $this->privDisableMagicQuotes(); - - // ----- Open the file in write mode - if (($v_result = $this->privOpenFd('wb')) != 1) - { - // ----- Return - return $v_result; - } + // ----- Create the central dir footer + if (($v_result = $this->privWriteCentralHeader($v_count + $v_central_dir['entries'], $v_size, $v_offset, $v_comment)) != 1) { + // ----- Reset the file list + unset($v_header_list); + $this->privSwapBackMagicQuotes(); - // ----- Add the list of files - $v_result = $this->privAddList($p_filedescr_list, $p_result_list, $p_options); + // ----- Return + return $v_result; + } - // ----- Close - $this->privCloseFd(); + // ----- Swap back the file descriptor + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; - // ----- Magic quotes trick - $this->privSwapBackMagicQuotes(); + // ----- Close + $this->privCloseFd(); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privAdd() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privAdd($p_filedescr_list, &$p_result_list, &$p_options) - { - $v_result=1; - $v_list_detail = array(); - - // ----- Look if the archive exists or is empty - if ((!is_file($this->zipname)) || (filesize($this->zipname) == 0)) - { + // ----- Close the temporary file + @fclose($v_zip_temp_fd); - // ----- Do a create - $v_result = $this->privCreate($p_filedescr_list, $p_result_list, $p_options); + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); - // ----- Return - return $v_result; - } - // ----- Magic quotes trick - $this->privDisableMagicQuotes(); + // ----- Delete the zip file + // TBC : I should test the result ... + @unlink($this->zipname); - // ----- Open the zip file - if (($v_result=$this->privOpenFd('rb')) != 1) - { - // ----- Magic quotes trick - $this->privSwapBackMagicQuotes(); + // ----- Rename the temporary file + // TBC : I should test the result ... + //@rename($v_zip_temp_name, $this->zipname); + PclZipUtilRename($v_zip_temp_name, $this->zipname); - // ----- Return - return $v_result; + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- - // ----- Read the central directory informations - $v_central_dir = array(); - if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) + // -------------------------------------------------------------------------------- + // Function : privOpenFd() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function privOpenFd($p_mode) { - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); - return $v_result; - } + $v_result = 1; - // ----- Go to beginning of File - @rewind($this->zip_fd); + // ----- Look if already open + if ($this->zip_fd != 0) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Zip file \'' . $this->zipname . '\' already open'); - // ----- Creates a temporay file - $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp'; + // ----- Return + return PclZip::errorCode(); + } - // ----- Open the temporary file in write mode - if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0) - { - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); + // ----- Open the zip file + if (($this->zip_fd = @fopen($this->zipname, $p_mode)) == 0) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \'' . $this->zipname . '\' in ' . $p_mode . ' mode'); - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_zip_temp_name.'\' in binary write mode'); + // ----- Return + return PclZip::errorCode(); + } - // ----- Return - return PclZip::errorCode(); + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- - // ----- Copy the files from the archive to the temporary file - // TBC : Here I should better append the file and go back to erase the central dir - $v_size = $v_central_dir['offset']; - while ($v_size != 0) + // -------------------------------------------------------------------------------- + // Function : privCloseFd() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function privCloseFd() { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = fread($this->zip_fd, $v_read_size); - @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); - $v_size -= $v_read_size; - } + $v_result = 1; - // ----- Swap the file descriptor - // Here is a trick : I swap the temporary fd with the zip fd, in order to use - // the following methods on the temporary fil and not the real archive - $v_swap = $this->zip_fd; - $this->zip_fd = $v_zip_temp_fd; - $v_zip_temp_fd = $v_swap; + if ($this->zip_fd != 0) { + @fclose($this->zip_fd); + } + $this->zip_fd = 0; - // ----- Add the files - $v_header_list = array(); - if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1) + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAddList() + // Description : + // $p_add_dir and $p_remove_dir will give the ability to memorize a path which is + // different from the real path of the file. This is usefull if you want to have PclTar + // running in any directory, and memorize relative path from an other directory. + // Parameters : + // $p_list : An array containing the file or directory names to add in the tar + // $p_result_list : list of added files with their properties (specially the status field) + // $p_add_dir : Path to add in the filename path archived + // $p_remove_dir : Path to remove in the filename path archived + // Return Values : + // -------------------------------------------------------------------------------- + // function privAddList($p_list, &$p_result_list, $p_add_dir, $p_remove_dir, $p_remove_all_dir, &$p_options) + public function privAddList($p_filedescr_list, &$p_result_list, &$p_options) { - fclose($v_zip_temp_fd); - $this->privCloseFd(); - @unlink($v_zip_temp_name); - $this->privSwapBackMagicQuotes(); + $v_result = 1; - // ----- Return - return $v_result; - } + // ----- Add the files + $v_header_list = array(); + if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1) { + // ----- Return + return $v_result; + } - // ----- Store the offset of the central dir - $v_offset = @ftell($this->zip_fd); + // ----- Store the offset of the central dir + $v_offset = @ftell($this->zip_fd); - // ----- Copy the block of file headers from the old archive - $v_size = $v_central_dir['size']; - while ($v_size != 0) - { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @fread($v_zip_temp_fd, $v_read_size); - @fwrite($this->zip_fd, $v_buffer, $v_read_size); - $v_size -= $v_read_size; - } + // ----- Create the Central Dir files header + for ($i = 0, $v_count = 0; $i < sizeof($v_header_list); $i++) { + // ----- Create the file header + if ($v_header_list[$i]['status'] == 'ok') { + if (($v_result = $this->privWriteCentralFileHeader($v_header_list[$i])) != 1) { + // ----- Return + return $v_result; + } + $v_count++; + } - // ----- Create the Central Dir files header - for ($i=0, $v_count=0; $iprivWriteCentralFileHeader($v_header_list[$i])) != 1) { - fclose($v_zip_temp_fd); - $this->privCloseFd(); - @unlink($v_zip_temp_name); - $this->privSwapBackMagicQuotes(); - - // ----- Return - return $v_result; - } - $v_count++; - } - - // ----- Transform the header to a 'usable' info - $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); - } + // ----- Transform the header to a 'usable' info + $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); + } - // ----- Zip file comment - $v_comment = $v_central_dir['comment']; - if (isset($p_options[PCLZIP_OPT_COMMENT])) { - $v_comment = $p_options[PCLZIP_OPT_COMMENT]; - } - if (isset($p_options[PCLZIP_OPT_ADD_COMMENT])) { - $v_comment = $v_comment.$p_options[PCLZIP_OPT_ADD_COMMENT]; - } - if (isset($p_options[PCLZIP_OPT_PREPEND_COMMENT])) { - $v_comment = $p_options[PCLZIP_OPT_PREPEND_COMMENT].$v_comment; - } + // ----- Zip file comment + $v_comment = ''; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } - // ----- Calculate the size of the central header - $v_size = @ftell($this->zip_fd)-$v_offset; + // ----- Calculate the size of the central header + $v_size = @ftell($this->zip_fd) - $v_offset; - // ----- Create the central dir footer - if (($v_result = $this->privWriteCentralHeader($v_count+$v_central_dir['entries'], $v_size, $v_offset, $v_comment)) != 1) - { - // ----- Reset the file list - unset($v_header_list); - $this->privSwapBackMagicQuotes(); + // ----- Create the central dir footer + if (($v_result = $this->privWriteCentralHeader($v_count, $v_size, $v_offset, $v_comment)) != 1) { + // ----- Reset the file list + unset($v_header_list); - // ----- Return - return $v_result; + // ----- Return + return $v_result; + } + + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAddFileList() + // Description : + // Parameters : + // $p_filedescr_list : An array containing the file description + // or directory names to add in the zip + // $p_result_list : list of added files with their properties (specially the status field) + // Return Values : + // -------------------------------------------------------------------------------- + public function privAddFileList($p_filedescr_list, &$p_result_list, &$p_options) + { + $v_result = 1; + $v_header = array(); - // ----- Swap back the file descriptor - $v_swap = $this->zip_fd; - $this->zip_fd = $v_zip_temp_fd; - $v_zip_temp_fd = $v_swap; + // ----- Recuperate the current number of elt in list + $v_nb = sizeof($p_result_list); - // ----- Close - $this->privCloseFd(); + // ----- Loop on the files + for ($j = 0; ($j < sizeof($p_filedescr_list)) && ($v_result == 1); $j++) { + // ----- Format the filename + $p_filedescr_list[$j]['filename'] = PclZipUtilTranslateWinPath($p_filedescr_list[$j]['filename'], false); - // ----- Close the temporary file - @fclose($v_zip_temp_fd); + // ----- Skip empty file names + // TBC : Can this be possible ? not checked in DescrParseAtt ? + if ($p_filedescr_list[$j]['filename'] == "") { + continue; + } - // ----- Magic quotes trick - $this->privSwapBackMagicQuotes(); + // ----- Check the filename + if (($p_filedescr_list[$j]['type'] != 'virtual_file') && (!file_exists($p_filedescr_list[$j]['filename']))) { + PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "File '" . $p_filedescr_list[$j]['filename'] . "' does not exist"); - // ----- Delete the zip file - // TBC : I should test the result ... - @unlink($this->zipname); + return PclZip::errorCode(); + } - // ----- Rename the temporary file - // TBC : I should test the result ... - //@rename($v_zip_temp_name, $this->zipname); - PclZipUtilRename($v_zip_temp_name, $this->zipname); + // ----- Look if it is a file or a dir with no all path remove option + // or a dir with all its path removed + // if ( (is_file($p_filedescr_list[$j]['filename'])) + // || ( is_dir($p_filedescr_list[$j]['filename']) + if (($p_filedescr_list[$j]['type'] == 'file') || ($p_filedescr_list[$j]['type'] == 'virtual_file') || (($p_filedescr_list[$j]['type'] == 'folder') && (!isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH]) || !$p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))) { - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privOpenFd() - // Description : - // Parameters : - // -------------------------------------------------------------------------------- - function privOpenFd($p_mode) - { - $v_result=1; - - // ----- Look if already open - if ($this->zip_fd != 0) - { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Zip file \''.$this->zipname.'\' already open'); + // ----- Add the file + $v_result = $this->privAddFile($p_filedescr_list[$j], $v_header, $p_options); + if ($v_result != 1) { + return $v_result; + } + + // ----- Store the file infos + $p_result_list[$v_nb++] = $v_header; + } + } - // ----- Return - return PclZip::errorCode(); + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- - // ----- Open the zip file - if (($this->zip_fd = @fopen($this->zipname, $p_mode)) == 0) + // -------------------------------------------------------------------------------- + // Function : privAddFile() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privAddFile($p_filedescr, &$p_header, &$p_options) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in '.$p_mode.' mode'); + $v_result = 1; - // ----- Return - return PclZip::errorCode(); - } + // ----- Working variable + $p_filename = $p_filedescr['filename']; - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privCloseFd() - // Description : - // Parameters : - // -------------------------------------------------------------------------------- - function privCloseFd() - { - $v_result=1; - - if ($this->zip_fd != 0) - @fclose($this->zip_fd); - $this->zip_fd = 0; + // TBC : Already done in the fileAtt check ... ? + if ($p_filename == "") { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file list parameter (invalid or empty list)"); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privAddList() - // Description : - // $p_add_dir and $p_remove_dir will give the ability to memorize a path which is - // different from the real path of the file. This is usefull if you want to have PclTar - // running in any directory, and memorize relative path from an other directory. - // Parameters : - // $p_list : An array containing the file or directory names to add in the tar - // $p_result_list : list of added files with their properties (specially the status field) - // $p_add_dir : Path to add in the filename path archived - // $p_remove_dir : Path to remove in the filename path archived - // Return Values : - // -------------------------------------------------------------------------------- -// function privAddList($p_list, &$p_result_list, $p_add_dir, $p_remove_dir, $p_remove_all_dir, &$p_options) - function privAddList($p_filedescr_list, &$p_result_list, &$p_options) - { - $v_result=1; - - // ----- Add the files - $v_header_list = array(); - if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1) - { - // ----- Return - return $v_result; - } + // ----- Return + return PclZip::errorCode(); + } - // ----- Store the offset of the central dir - $v_offset = @ftell($this->zip_fd); + // ----- Look for a stored different filename + /* TBC : Removed + if (isset($p_filedescr['stored_filename'])) { + $v_stored_filename = $p_filedescr['stored_filename']; + } else { + $v_stored_filename = $p_filedescr['stored_filename']; + } + */ - // ----- Create the Central Dir files header - for ($i=0,$v_count=0; $iprivWriteCentralFileHeader($v_header_list[$i])) != 1) { - // ----- Return - return $v_result; + // ----- Set the file properties + clearstatcache(); + $p_header['version'] = 20; + $p_header['version_extracted'] = 10; + $p_header['flag'] = 0; + $p_header['compression'] = 0; + $p_header['crc'] = 0; + $p_header['compressed_size'] = 0; + $p_header['filename_len'] = strlen($p_filename); + $p_header['extra_len'] = 0; + $p_header['disk'] = 0; + $p_header['internal'] = 0; + $p_header['offset'] = 0; + $p_header['filename'] = $p_filename; + // TBC : Removed $p_header['stored_filename'] = $v_stored_filename; + $p_header['stored_filename'] = $p_filedescr['stored_filename']; + $p_header['extra'] = ''; + $p_header['status'] = 'ok'; + $p_header['index'] = -1; + + // ----- Look for regular file + if ($p_filedescr['type'] == 'file') { + $p_header['external'] = 0x00000000; + $p_header['size'] = filesize($p_filename); + + // ----- Look for regular folder + } elseif ($p_filedescr['type'] == 'folder') { + $p_header['external'] = 0x00000010; + $p_header['mtime'] = filemtime($p_filename); + $p_header['size'] = filesize($p_filename); + + // ----- Look for virtual file + } elseif ($p_filedescr['type'] == 'virtual_file') { + $p_header['external'] = 0x00000000; + $p_header['size'] = strlen($p_filedescr['content']); } - $v_count++; - } - // ----- Transform the header to a 'usable' info - $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); - } + // ----- Look for filetime + if (isset($p_filedescr['mtime'])) { + $p_header['mtime'] = $p_filedescr['mtime']; + } elseif ($p_filedescr['type'] == 'virtual_file') { + $p_header['mtime'] = time(); + } else { + $p_header['mtime'] = filemtime($p_filename); + } - // ----- Zip file comment - $v_comment = ''; - if (isset($p_options[PCLZIP_OPT_COMMENT])) { - $v_comment = $p_options[PCLZIP_OPT_COMMENT]; - } + // ------ Look for file comment + if (isset($p_filedescr['comment'])) { + $p_header['comment_len'] = strlen($p_filedescr['comment']); + $p_header['comment'] = $p_filedescr['comment']; + } else { + $p_header['comment_len'] = 0; + $p_header['comment'] = ''; + } - // ----- Calculate the size of the central header - $v_size = @ftell($this->zip_fd)-$v_offset; + // ----- Look for pre-add callback + if (isset($p_options[PCLZIP_CB_PRE_ADD])) { - // ----- Create the central dir footer - if (($v_result = $this->privWriteCentralHeader($v_count, $v_size, $v_offset, $v_comment)) != 1) - { - // ----- Reset the file list - unset($v_header_list); + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_header, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_PRE_ADD].'(PCLZIP_CB_PRE_ADD, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_ADD](PCLZIP_CB_PRE_ADD, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_header['status'] = "skipped"; + $v_result = 1; + } - // ----- Return - return $v_result; - } + // ----- Update the informations + // Only some fields can be modified + if ($p_header['stored_filename'] != $v_local_header['stored_filename']) { + $p_header['stored_filename'] = PclZipUtilPathReduction($v_local_header['stored_filename']); + } + } - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privAddFileList() - // Description : - // Parameters : - // $p_filedescr_list : An array containing the file description - // or directory names to add in the zip - // $p_result_list : list of added files with their properties (specially the status field) - // Return Values : - // -------------------------------------------------------------------------------- - function privAddFileList($p_filedescr_list, &$p_result_list, &$p_options) - { - $v_result=1; - $v_header = array(); - - // ----- Recuperate the current number of elt in list - $v_nb = sizeof($p_result_list); - - // ----- Loop on the files - for ($j=0; ($jprivAddFile($p_filedescr_list[$j], $v_header, - $p_options); - if ($v_result != 1) { - return $v_result; + // ----- Look for empty stored filename + if ($p_header['stored_filename'] == "") { + $p_header['status'] = "filtered"; } - // ----- Store the file infos - $p_result_list[$v_nb++] = $v_header; - } - } + // ----- Check the path length + if (strlen($p_header['stored_filename']) > 0xFF) { + $p_header['status'] = 'filename_too_long'; + } - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privAddFile() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privAddFile($p_filedescr, &$p_header, &$p_options) - { - $v_result=1; - - // ----- Working variable - $p_filename = $p_filedescr['filename']; - - // TBC : Already done in the fileAtt check ... ? - if ($p_filename == "") { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file list parameter (invalid or empty list)"); - - // ----- Return - return PclZip::errorCode(); - } + // ----- Look if no error, or file not skipped + if ($p_header['status'] == 'ok') { - // ----- Look for a stored different filename - /* TBC : Removed - if (isset($p_filedescr['stored_filename'])) { - $v_stored_filename = $p_filedescr['stored_filename']; - } - else { - $v_stored_filename = $p_filedescr['stored_filename']; - } - */ - - // ----- Set the file properties - clearstatcache(); - $p_header['version'] = 20; - $p_header['version_extracted'] = 10; - $p_header['flag'] = 0; - $p_header['compression'] = 0; - $p_header['crc'] = 0; - $p_header['compressed_size'] = 0; - $p_header['filename_len'] = strlen($p_filename); - $p_header['extra_len'] = 0; - $p_header['disk'] = 0; - $p_header['internal'] = 0; - $p_header['offset'] = 0; - $p_header['filename'] = $p_filename; -// TBC : Removed $p_header['stored_filename'] = $v_stored_filename; - $p_header['stored_filename'] = $p_filedescr['stored_filename']; - $p_header['extra'] = ''; - $p_header['status'] = 'ok'; - $p_header['index'] = -1; - - // ----- Look for regular file - if ($p_filedescr['type']=='file') { - $p_header['external'] = 0x00000000; - $p_header['size'] = filesize($p_filename); - } + // ----- Look for a file + if ($p_filedescr['type'] == 'file') { + // ----- Look for using temporary file to zip + if ((!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON]) || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_header['size'])))) { + $v_result = $this->privAddFileUsingTempFile($p_filedescr, $p_header, $p_options); + if ($v_result < PCLZIP_ERR_NO_ERROR) { + return $v_result; + } - // ----- Look for regular folder - else if ($p_filedescr['type']=='folder') { - $p_header['external'] = 0x00000010; - $p_header['mtime'] = filemtime($p_filename); - $p_header['size'] = filesize($p_filename); - } + // ----- Use "in memory" zip algo + } else { - // ----- Look for virtual file - else if ($p_filedescr['type'] == 'virtual_file') { - $p_header['external'] = 0x00000000; - $p_header['size'] = strlen($p_filedescr['content']); - } + // ----- Open the source file + if (($v_file = @fopen($p_filename, "rb")) == 0) { + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode"); + return PclZip::errorCode(); + } - // ----- Look for filetime - if (isset($p_filedescr['mtime'])) { - $p_header['mtime'] = $p_filedescr['mtime']; - } - else if ($p_filedescr['type'] == 'virtual_file') { - $p_header['mtime'] = time(); - } - else { - $p_header['mtime'] = filemtime($p_filename); - } + // ----- Read the file content + $v_content = @fread($v_file, $p_header['size']); - // ------ Look for file comment - if (isset($p_filedescr['comment'])) { - $p_header['comment_len'] = strlen($p_filedescr['comment']); - $p_header['comment'] = $p_filedescr['comment']; - } - else { - $p_header['comment_len'] = 0; - $p_header['comment'] = ''; - } + // ----- Close the file + @fclose($v_file); - // ----- Look for pre-add callback - if (isset($p_options[PCLZIP_CB_PRE_ADD])) { - - // ----- Generate a local information - $v_local_header = array(); - $this->privConvertHeader2FileInfo($p_header, $v_local_header); - - // ----- Call the callback - // Here I do not use call_user_func() because I need to send a reference to the - // header. -// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_ADD].'(PCLZIP_CB_PRE_ADD, $v_local_header);'); - $v_result = $p_options[PCLZIP_CB_PRE_ADD](PCLZIP_CB_PRE_ADD, $v_local_header); - if ($v_result == 0) { - // ----- Change the file status - $p_header['status'] = "skipped"; - $v_result = 1; - } + // ----- Calculate the CRC + $p_header['crc'] = @crc32($v_content); - // ----- Update the informations - // Only some fields can be modified - if ($p_header['stored_filename'] != $v_local_header['stored_filename']) { - $p_header['stored_filename'] = PclZipUtilPathReduction($v_local_header['stored_filename']); - } - } + // ----- Look for no compression + if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) { + // ----- Set header parameters + $p_header['compressed_size'] = $p_header['size']; + $p_header['compression'] = 0; - // ----- Look for empty stored filename - if ($p_header['stored_filename'] == "") { - $p_header['status'] = "filtered"; - } + // ----- Look for normal compression + } else { + // ----- Compress the content + $v_content = @gzdeflate($v_content); - // ----- Check the path length - if (strlen($p_header['stored_filename']) > 0xFF) { - $p_header['status'] = 'filename_too_long'; - } + // ----- Set header parameters + $p_header['compressed_size'] = strlen($v_content); + $p_header['compression'] = 8; + } - // ----- Look if no error, or file not skipped - if ($p_header['status'] == 'ok') { - - // ----- Look for a file - if ($p_filedescr['type'] == 'file') { - // ----- Look for using temporary file to zip - if ( (!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) - && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON]) - || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) - && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_header['size'])) ) ) { - $v_result = $this->privAddFileUsingTempFile($p_filedescr, $p_header, $p_options); - if ($v_result < PCLZIP_ERR_NO_ERROR) { - return $v_result; - } + // ----- Call the header generation + if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { + @fclose($v_file); + + return $v_result; + } + + // ----- Write the compressed (or not) content + @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']); + + } + + // ----- Look for a virtual file (a file from string) + } elseif ($p_filedescr['type'] == 'virtual_file') { + + $v_content = $p_filedescr['content']; + + // ----- Calculate the CRC + $p_header['crc'] = @crc32($v_content); + + // ----- Look for no compression + if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) { + // ----- Set header parameters + $p_header['compressed_size'] = $p_header['size']; + $p_header['compression'] = 0; + + // ----- Look for normal compression + } else { + // ----- Compress the content + $v_content = @gzdeflate($v_content); + + // ----- Set header parameters + $p_header['compressed_size'] = strlen($v_content); + $p_header['compression'] = 8; + } + + // ----- Call the header generation + if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { + @fclose($v_file); + + return $v_result; + } + + // ----- Write the compressed (or not) content + @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']); + + // ----- Look for a directory + } elseif ($p_filedescr['type'] == 'folder') { + // ----- Look for directory last '/' + if (@substr($p_header['stored_filename'], -1) != '/') { + $p_header['stored_filename'] .= '/'; + } + + // ----- Set the file properties + $p_header['size'] = 0; + //$p_header['external'] = 0x41FF0010; // Value for a folder : to be checked + $p_header['external'] = 0x00000010; // Value for a folder : to be checked + + // ----- Call the header generation + if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { + return $v_result; + } + } } - // ----- Use "in memory" zip algo - else { + // ----- Look for post-add callback + if (isset($p_options[PCLZIP_CB_POST_ADD])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_header, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_POST_ADD].'(PCLZIP_CB_POST_ADD, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_ADD](PCLZIP_CB_POST_ADD, $v_local_header); + if ($v_result == 0) { + // ----- Ignored + $v_result = 1; + } + + // ----- Update the informations + // Nothing can be modified + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAddFileUsingTempFile() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privAddFileUsingTempFile($p_filedescr, &$p_header, &$p_options) + { + $v_result = PCLZIP_ERR_NO_ERROR; + + // ----- Working variable + $p_filename = $p_filedescr['filename']; // ----- Open the source file if (($v_file = @fopen($p_filename, "rb")) == 0) { - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode"); - return PclZip::errorCode(); + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode"); + + return PclZip::errorCode(); + } + + // ----- Creates a compressed temporary file + $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.gz'; + if (($v_file_compressed = @gzopen($v_gzip_temp_name, "wb")) == 0) { + fclose($v_file); + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary write mode'); + + return PclZip::errorCode(); } - // ----- Read the file content - $v_content = @fread($v_file, $p_header['size']); + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = filesize($p_filename); + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($v_file, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @gzputs($v_file_compressed, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } // ----- Close the file @fclose($v_file); + @gzclose($v_file_compressed); - // ----- Calculate the CRC - $p_header['crc'] = @crc32($v_content); + // ----- Check the minimum file size + if (filesize($v_gzip_temp_name) < 18) { + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'gzip temporary file \'' . $v_gzip_temp_name . '\' has invalid filesize - should be minimum 18 bytes'); - // ----- Look for no compression - if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) { - // ----- Set header parameters - $p_header['compressed_size'] = $p_header['size']; - $p_header['compression'] = 0; + return PclZip::errorCode(); } - // ----- Look for normal compression - else { - // ----- Compress the content - $v_content = @gzdeflate($v_content); + // ----- Extract the compressed attributes + if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) { + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary read mode'); - // ----- Set header parameters - $p_header['compressed_size'] = strlen($v_content); - $p_header['compression'] = 8; + return PclZip::errorCode(); } + // ----- Read the gzip file header + $v_binary_data = @fread($v_file_compressed, 10); + $v_data_header = unpack('a1id1/a1id2/a1cm/a1flag/Vmtime/a1xfl/a1os', $v_binary_data); + + // ----- Check some parameters + $v_data_header['os'] = bin2hex($v_data_header['os']); + + // ----- Read the gzip file footer + @fseek($v_file_compressed, filesize($v_gzip_temp_name) - 8); + $v_binary_data = @fread($v_file_compressed, 8); + $v_data_footer = unpack('Vcrc/Vcompressed_size', $v_binary_data); + + // ----- Set the attributes + $p_header['compression'] = ord($v_data_header['cm']); + //$p_header['mtime'] = $v_data_header['mtime']; + $p_header['crc'] = $v_data_footer['crc']; + $p_header['compressed_size'] = filesize($v_gzip_temp_name) - 18; + + // ----- Close the file + @fclose($v_file_compressed); + // ----- Call the header generation if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { - @fclose($v_file); - return $v_result; + return $v_result; + } + + // ----- Add the compressed data + if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) { + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary read mode'); + + return PclZip::errorCode(); + } + + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + fseek($v_file_compressed, 10); + $v_size = $p_header['compressed_size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($v_file_compressed, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($this->zip_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Close the file + @fclose($v_file_compressed); + + // ----- Unlink the temporary file + @unlink($v_gzip_temp_name); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCalculateStoredFilename() + // Description : + // Based on file descriptor properties and global options, this method + // calculate the filename that will be stored in the archive. + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privCalculateStoredFilename(&$p_filedescr, &$p_options) + { + $v_result = 1; + + // ----- Working variables + $p_filename = $p_filedescr['filename']; + if (isset($p_options[PCLZIP_OPT_ADD_PATH])) { + $p_add_dir = $p_options[PCLZIP_OPT_ADD_PATH]; + } else { + $p_add_dir = ''; + } + if (isset($p_options[PCLZIP_OPT_REMOVE_PATH])) { + $p_remove_dir = $p_options[PCLZIP_OPT_REMOVE_PATH]; + } else { + $p_remove_dir = ''; + } + if (isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { + $p_remove_all_dir = $p_options[PCLZIP_OPT_REMOVE_ALL_PATH]; + } else { + $p_remove_all_dir = 0; + } + + // ----- Look for full name change + if (isset($p_filedescr['new_full_name'])) { + // ----- Remove drive letter if any + $v_stored_filename = PclZipUtilTranslateWinPath($p_filedescr['new_full_name']); + + // ----- Look for path and/or short name change + } else { + + // ----- Look for short name change + // Its when we cahnge just the filename but not the path + if (isset($p_filedescr['new_short_name'])) { + $v_path_info = pathinfo($p_filename); + $v_dir = ''; + if ($v_path_info['dirname'] != '') { + $v_dir = $v_path_info['dirname'] . '/'; + } + $v_stored_filename = $v_dir . $p_filedescr['new_short_name']; + } else { + // ----- Calculate the stored filename + $v_stored_filename = $p_filename; + } + + // ----- Look for all path to remove + if ($p_remove_all_dir) { + $v_stored_filename = basename($p_filename); + + // ----- Look for partial path remove + } elseif ($p_remove_dir != "") { + if (substr($p_remove_dir, -1) != '/') { + $p_remove_dir .= "/"; + } + + if ((substr($p_filename, 0, 2) == "./") || (substr($p_remove_dir, 0, 2) == "./")) { + + if ((substr($p_filename, 0, 2) == "./") && (substr($p_remove_dir, 0, 2) != "./")) { + $p_remove_dir = "./" . $p_remove_dir; + } + if ((substr($p_filename, 0, 2) != "./") && (substr($p_remove_dir, 0, 2) == "./")) { + $p_remove_dir = substr($p_remove_dir, 2); + } + } + + $v_compare = PclZipUtilPathInclusion($p_remove_dir, $v_stored_filename); + if ($v_compare > 0) { + if ($v_compare == 2) { + $v_stored_filename = ""; + } else { + $v_stored_filename = substr($v_stored_filename, strlen($p_remove_dir)); + } + } + } + + // ----- Remove drive letter if any + $v_stored_filename = PclZipUtilTranslateWinPath($v_stored_filename); + + // ----- Look for path to add + if ($p_add_dir != "") { + if (substr($p_add_dir, -1) == "/") { + $v_stored_filename = $p_add_dir . $v_stored_filename; + } else { + $v_stored_filename = $p_add_dir . "/" . $v_stored_filename; + } + } + } + + // ----- Filename (reduce the path of stored name) + $v_stored_filename = PclZipUtilPathReduction($v_stored_filename); + $p_filedescr['stored_filename'] = $v_stored_filename; + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privWriteFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privWriteFileHeader(&$p_header) + { + $v_result = 1; + + // ----- Store the offset position of the file + $p_header['offset'] = ftell($this->zip_fd); + + // ----- Transform UNIX mtime to DOS format mdate/mtime + $v_date = getdate($p_header['mtime']); + $v_mtime = ($v_date['hours'] << 11) + ($v_date['minutes'] << 5) + $v_date['seconds'] / 2; + $v_mdate = (($v_date['year'] - 1980) << 9) + ($v_date['mon'] << 5) + $v_date['mday']; + + // ----- Packed data + $v_binary_data = pack("VvvvvvVVVvv", 0x04034b50, $p_header['version_extracted'], $p_header['flag'], $p_header['compression'], $v_mtime, $v_mdate, $p_header['crc'], $p_header['compressed_size'], $p_header['size'], strlen($p_header['stored_filename']), $p_header['extra_len']); + + // ----- Write the first 148 bytes of the header in the archive + fputs($this->zip_fd, $v_binary_data, 30); + + // ----- Write the variable fields + if (strlen($p_header['stored_filename']) != 0) { + fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename'])); + } + if ($p_header['extra_len'] != 0) { + fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']); } - // ----- Write the compressed (or not) content - @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']); + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privWriteCentralFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privWriteCentralFileHeader(&$p_header) + { + $v_result = 1; + + // TBC + //for (reset($p_header); $key = key($p_header); next($p_header)) { + //} + + // ----- Transform UNIX mtime to DOS format mdate/mtime + $v_date = getdate($p_header['mtime']); + $v_mtime = ($v_date['hours'] << 11) + ($v_date['minutes'] << 5) + $v_date['seconds'] / 2; + $v_mdate = (($v_date['year'] - 1980) << 9) + ($v_date['mon'] << 5) + $v_date['mday']; + + // ----- Packed data + $v_binary_data = pack("VvvvvvvVVVvvvvvVV", 0x02014b50, $p_header['version'], $p_header['version_extracted'], $p_header['flag'], $p_header['compression'], $v_mtime, $v_mdate, $p_header['crc'], $p_header['compressed_size'], $p_header['size'], strlen($p_header['stored_filename']), $p_header['extra_len'], $p_header['comment_len'], $p_header['disk'], $p_header['internal'], $p_header['external'], $p_header['offset']); + // ----- Write the 42 bytes of the header in the zip file + fputs($this->zip_fd, $v_binary_data, 46); + + // ----- Write the variable fields + if (strlen($p_header['stored_filename']) != 0) { + fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename'])); + } + if ($p_header['extra_len'] != 0) { + fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']); + } + if ($p_header['comment_len'] != 0) { + fputs($this->zip_fd, $p_header['comment'], $p_header['comment_len']); } - } + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privWriteCentralHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privWriteCentralHeader($p_nb_entries, $p_size, $p_offset, $p_comment) + { + $v_result = 1; - // ----- Look for a virtual file (a file from string) - else if ($p_filedescr['type'] == 'virtual_file') { + // ----- Packed data + $v_binary_data = pack("VvvvvVVv", 0x06054b50, 0, 0, $p_nb_entries, $p_nb_entries, $p_size, $p_offset, strlen($p_comment)); - $v_content = $p_filedescr['content']; + // ----- Write the 22 bytes of the header in the zip file + fputs($this->zip_fd, $v_binary_data, 22); - // ----- Calculate the CRC - $p_header['crc'] = @crc32($v_content); + // ----- Write the variable fields + if (strlen($p_comment) != 0) { + fputs($this->zip_fd, $p_comment, strlen($p_comment)); + } - // ----- Look for no compression - if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) { - // ----- Set header parameters - $p_header['compressed_size'] = $p_header['size']; - $p_header['compression'] = 0; + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privList() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privList(&$p_list) + { + $v_result = 1; + + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Open the zip file + if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0) { + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \'' . $this->zipname . '\' in binary read mode'); + + // ----- Return + return PclZip::errorCode(); } - // ----- Look for normal compression - else { - // ----- Compress the content - $v_content = @gzdeflate($v_content); + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + $this->privSwapBackMagicQuotes(); - // ----- Set header parameters - $p_header['compressed_size'] = strlen($v_content); - $p_header['compression'] = 8; + return $v_result; } - // ----- Call the header generation - if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { - @fclose($v_file); - return $v_result; - } + // ----- Go to beginning of Central Dir + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_central_dir['offset'])) { + $this->privSwapBackMagicQuotes(); - // ----- Write the compressed (or not) content - @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']); - } + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); - // ----- Look for a directory - else if ($p_filedescr['type'] == 'folder') { - // ----- Look for directory last '/' - if (@substr($p_header['stored_filename'], -1) != '/') { - $p_header['stored_filename'] .= '/'; + // ----- Return + return PclZip::errorCode(); } - // ----- Set the file properties - $p_header['size'] = 0; - //$p_header['external'] = 0x41FF0010; // Value for a folder : to be checked - $p_header['external'] = 0x00000010; // Value for a folder : to be checked + // ----- Read each entry + for ($i = 0; $i < $v_central_dir['entries']; $i++) { + // ----- Read the file header + if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1) { + $this->privSwapBackMagicQuotes(); - // ----- Call the header generation - if (($v_result = $this->privWriteFileHeader($p_header)) != 1) - { - return $v_result; - } - } - } + return $v_result; + } + $v_header['index'] = $i; - // ----- Look for post-add callback - if (isset($p_options[PCLZIP_CB_POST_ADD])) { + // ----- Get the only interesting attributes + $this->privConvertHeader2FileInfo($v_header, $p_list[$i]); + unset($v_header); + } - // ----- Generate a local information - $v_local_header = array(); - $this->privConvertHeader2FileInfo($p_header, $v_local_header); + // ----- Close the zip file + $this->privCloseFd(); - // ----- Call the callback - // Here I do not use call_user_func() because I need to send a reference to the - // header. -// eval('$v_result = '.$p_options[PCLZIP_CB_POST_ADD].'(PCLZIP_CB_POST_ADD, $v_local_header);'); - $v_result = $p_options[PCLZIP_CB_POST_ADD](PCLZIP_CB_POST_ADD, $v_local_header); - if ($v_result == 0) { - // ----- Ignored - $v_result = 1; - } + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); - // ----- Update the informations - // Nothing can be modified + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privConvertHeader2FileInfo() + // Description : + // This function takes the file informations from the central directory + // entries and extract the interesting parameters that will be given back. + // The resulting file infos are set in the array $p_info + // $p_info['filename'] : Filename with full path. Given by user (add), + // extracted in the filesystem (extract). + // $p_info['stored_filename'] : Stored filename in the archive. + // $p_info['size'] = Size of the file. + // $p_info['compressed_size'] = Compressed size of the file. + // $p_info['mtime'] = Last modification date of the file. + // $p_info['comment'] = Comment associated with the file. + // $p_info['folder'] = true/false : indicates if the entry is a folder or not. + // $p_info['status'] = status of the action on the file. + // $p_info['crc'] = CRC of the file content. + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privConvertHeader2FileInfo($p_header, &$p_info) + { + $v_result = 1; - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privAddFileUsingTempFile() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privAddFileUsingTempFile($p_filedescr, &$p_header, &$p_options) - { - $v_result=PCLZIP_ERR_NO_ERROR; - - // ----- Working variable - $p_filename = $p_filedescr['filename']; - - - // ----- Open the source file - if (($v_file = @fopen($p_filename, "rb")) == 0) { - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode"); - return PclZip::errorCode(); - } + // ----- Get the interesting attributes + $v_temp_path = PclZipUtilPathReduction($p_header['filename']); + $p_info['filename'] = $v_temp_path; + $v_temp_path = PclZipUtilPathReduction($p_header['stored_filename']); + $p_info['stored_filename'] = $v_temp_path; + $p_info['size'] = $p_header['size']; + $p_info['compressed_size'] = $p_header['compressed_size']; + $p_info['mtime'] = $p_header['mtime']; + $p_info['comment'] = $p_header['comment']; + $p_info['folder'] = (($p_header['external'] & 0x00000010) == 0x00000010); + $p_info['index'] = $p_header['index']; + $p_info['status'] = $p_header['status']; + $p_info['crc'] = $p_header['crc']; - // ----- Creates a compressed temporary file - $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.gz'; - if (($v_file_compressed = @gzopen($v_gzip_temp_name, "wb")) == 0) { - fclose($v_file); - PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary write mode'); - return PclZip::errorCode(); + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractByRule() + // Description : + // Extract a file or directory depending of rules (by index, by name, ...) + // Parameters : + // $p_file_list : An array where will be placed the properties of each + // extracted file + // $p_path : Path to add while writing the extracted files + // $p_remove_path : Path to remove (from the file memorized path) while writing the + // extracted files. If the path does not match the file path, + // the file is extracted with its memorized path. + // $p_remove_path does not apply to 'list' mode. + // $p_path and $p_remove_path are commulative. + // Return Values : + // 1 on success,0 or less on error (see error code list) + // -------------------------------------------------------------------------------- + public function privExtractByRule(&$p_file_list, $p_path, $p_remove_path, $p_remove_all_path, &$p_options) + { + $v_result = 1; - // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks - $v_size = filesize($p_filename); - while ($v_size != 0) { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @fread($v_file, $v_read_size); - //$v_binary_data = pack('a'.$v_read_size, $v_buffer); - @gzputs($v_file_compressed, $v_buffer, $v_read_size); - $v_size -= $v_read_size; - } + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); - // ----- Close the file - @fclose($v_file); - @gzclose($v_file_compressed); + // ----- Check the path + if (($p_path == "") || ((substr($p_path, 0, 1) != "/") && (substr($p_path, 0, 3) != "../") && (substr($p_path, 1, 2) != ":/"))) { + $p_path = "./" . $p_path; + } - // ----- Check the minimum file size - if (filesize($v_gzip_temp_name) < 18) { - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'gzip temporary file \''.$v_gzip_temp_name.'\' has invalid filesize - should be minimum 18 bytes'); - return PclZip::errorCode(); - } + // ----- Reduce the path last (and duplicated) '/' + if (($p_path != "./") && ($p_path != "/")) { + // ----- Look for the path end '/' + while (substr($p_path, -1) == "/") { + $p_path = substr($p_path, 0, strlen($p_path) - 1); + } + } - // ----- Extract the compressed attributes - if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) { - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode'); - return PclZip::errorCode(); - } + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != "") && (substr($p_remove_path, -1) != '/')) { + $p_remove_path .= '/'; + } + $p_remove_path_size = strlen($p_remove_path); - // ----- Read the gzip file header - $v_binary_data = @fread($v_file_compressed, 10); - $v_data_header = unpack('a1id1/a1id2/a1cm/a1flag/Vmtime/a1xfl/a1os', $v_binary_data); + // ----- Open the zip file + if (($v_result = $this->privOpenFd('rb')) != 1) { + $this->privSwapBackMagicQuotes(); - // ----- Check some parameters - $v_data_header['os'] = bin2hex($v_data_header['os']); + return $v_result; + } - // ----- Read the gzip file footer - @fseek($v_file_compressed, filesize($v_gzip_temp_name)-8); - $v_binary_data = @fread($v_file_compressed, 8); - $v_data_footer = unpack('Vcrc/Vcompressed_size', $v_binary_data); + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); - // ----- Set the attributes - $p_header['compression'] = ord($v_data_header['cm']); - //$p_header['mtime'] = $v_data_header['mtime']; - $p_header['crc'] = $v_data_footer['crc']; - $p_header['compressed_size'] = filesize($v_gzip_temp_name)-18; + return $v_result; + } - // ----- Close the file - @fclose($v_file_compressed); + // ----- Start at beginning of Central Dir + $v_pos_entry = $v_central_dir['offset']; - // ----- Call the header generation - if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { - return $v_result; - } + // ----- Read each entry + $j_start = 0; + for ($i = 0, $v_nb_extracted = 0; $i < $v_central_dir['entries']; $i++) { - // ----- Add the compressed data - if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) - { - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode'); - return PclZip::errorCode(); - } + // ----- Read next Central dir entry + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_pos_entry)) { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); - // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks - fseek($v_file_compressed, 10); - $v_size = $p_header['compressed_size']; - while ($v_size != 0) - { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @fread($v_file_compressed, $v_read_size); - //$v_binary_data = pack('a'.$v_read_size, $v_buffer); - @fwrite($this->zip_fd, $v_buffer, $v_read_size); - $v_size -= $v_read_size; - } + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); - // ----- Close the file - @fclose($v_file_compressed); + // ----- Return + return PclZip::errorCode(); + } - // ----- Unlink the temporary file - @unlink($v_gzip_temp_name); + // ----- Read the file header + $v_header = array(); + if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privCalculateStoredFilename() - // Description : - // Based on file descriptor properties and global options, this method - // calculate the filename that will be stored in the archive. - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privCalculateStoredFilename(&$p_filedescr, &$p_options) - { - $v_result=1; - - // ----- Working variables - $p_filename = $p_filedescr['filename']; - if (isset($p_options[PCLZIP_OPT_ADD_PATH])) { - $p_add_dir = $p_options[PCLZIP_OPT_ADD_PATH]; - } - else { - $p_add_dir = ''; - } - if (isset($p_options[PCLZIP_OPT_REMOVE_PATH])) { - $p_remove_dir = $p_options[PCLZIP_OPT_REMOVE_PATH]; - } - else { - $p_remove_dir = ''; - } - if (isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { - $p_remove_all_dir = $p_options[PCLZIP_OPT_REMOVE_ALL_PATH]; - } - else { - $p_remove_all_dir = 0; - } + return $v_result; + } + // ----- Store the index + $v_header['index'] = $i; - // ----- Look for full name change - if (isset($p_filedescr['new_full_name'])) { - // ----- Remove drive letter if any - $v_stored_filename = PclZipUtilTranslateWinPath($p_filedescr['new_full_name']); - } + // ----- Store the file position + $v_pos_entry = ftell($this->zip_fd); - // ----- Look for path and/or short name change - else { - - // ----- Look for short name change - // Its when we cahnge just the filename but not the path - if (isset($p_filedescr['new_short_name'])) { - $v_path_info = pathinfo($p_filename); - $v_dir = ''; - if ($v_path_info['dirname'] != '') { - $v_dir = $v_path_info['dirname'].'/'; - } - $v_stored_filename = $v_dir.$p_filedescr['new_short_name']; - } - else { - // ----- Calculate the stored filename - $v_stored_filename = $p_filename; - } - - // ----- Look for all path to remove - if ($p_remove_all_dir) { - $v_stored_filename = basename($p_filename); - } - // ----- Look for partial path remove - else if ($p_remove_dir != "") { - if (substr($p_remove_dir, -1) != '/') - $p_remove_dir .= "/"; - - if ( (substr($p_filename, 0, 2) == "./") - || (substr($p_remove_dir, 0, 2) == "./")) { - - if ( (substr($p_filename, 0, 2) == "./") - && (substr($p_remove_dir, 0, 2) != "./")) { - $p_remove_dir = "./".$p_remove_dir; - } - if ( (substr($p_filename, 0, 2) != "./") - && (substr($p_remove_dir, 0, 2) == "./")) { - $p_remove_dir = substr($p_remove_dir, 2); - } - } - - $v_compare = PclZipUtilPathInclusion($p_remove_dir, - $v_stored_filename); - if ($v_compare > 0) { - if ($v_compare == 2) { - $v_stored_filename = ""; - } - else { - $v_stored_filename = substr($v_stored_filename, - strlen($p_remove_dir)); - } - } - } - - // ----- Remove drive letter if any - $v_stored_filename = PclZipUtilTranslateWinPath($v_stored_filename); - - // ----- Look for path to add - if ($p_add_dir != "") { - if (substr($p_add_dir, -1) == "/") - $v_stored_filename = $p_add_dir.$v_stored_filename; - else - $v_stored_filename = $p_add_dir."/".$v_stored_filename; - } - } + // ----- Look for the specific extract rules + $v_extract = false; - // ----- Filename (reduce the path of stored name) - $v_stored_filename = PclZipUtilPathReduction($v_stored_filename); - $p_filedescr['stored_filename'] = $v_stored_filename; + // ----- Look for extract by name rule + if ((isset($p_options[PCLZIP_OPT_BY_NAME])) && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) { - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privWriteFileHeader() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privWriteFileHeader(&$p_header) - { - $v_result=1; - - // ----- Store the offset position of the file - $p_header['offset'] = ftell($this->zip_fd); - - // ----- Transform UNIX mtime to DOS format mdate/mtime - $v_date = getdate($p_header['mtime']); - $v_mtime = ($v_date['hours']<<11) + ($v_date['minutes']<<5) + $v_date['seconds']/2; - $v_mdate = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday']; - - // ----- Packed data - $v_binary_data = pack("VvvvvvVVVvv", 0x04034b50, - $p_header['version_extracted'], $p_header['flag'], - $p_header['compression'], $v_mtime, $v_mdate, - $p_header['crc'], $p_header['compressed_size'], - $p_header['size'], - strlen($p_header['stored_filename']), - $p_header['extra_len']); - - // ----- Write the first 148 bytes of the header in the archive - fputs($this->zip_fd, $v_binary_data, 30); - - // ----- Write the variable fields - if (strlen($p_header['stored_filename']) != 0) - { - fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename'])); - } - if ($p_header['extra_len'] != 0) - { - fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']); - } + // ----- Look if the filename is in the list + for ($j = 0; ($j < sizeof($p_options[PCLZIP_OPT_BY_NAME])) && (!$v_extract); $j++) { - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privWriteCentralFileHeader() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privWriteCentralFileHeader(&$p_header) - { - $v_result=1; - - // TBC - //for(reset($p_header); $key = key($p_header); next($p_header)) { - //} - - // ----- Transform UNIX mtime to DOS format mdate/mtime - $v_date = getdate($p_header['mtime']); - $v_mtime = ($v_date['hours']<<11) + ($v_date['minutes']<<5) + $v_date['seconds']/2; - $v_mdate = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday']; - - - // ----- Packed data - $v_binary_data = pack("VvvvvvvVVVvvvvvVV", 0x02014b50, - $p_header['version'], $p_header['version_extracted'], - $p_header['flag'], $p_header['compression'], - $v_mtime, $v_mdate, $p_header['crc'], - $p_header['compressed_size'], $p_header['size'], - strlen($p_header['stored_filename']), - $p_header['extra_len'], $p_header['comment_len'], - $p_header['disk'], $p_header['internal'], - $p_header['external'], $p_header['offset']); - - // ----- Write the 42 bytes of the header in the zip file - fputs($this->zip_fd, $v_binary_data, 46); - - // ----- Write the variable fields - if (strlen($p_header['stored_filename']) != 0) - { - fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename'])); - } - if ($p_header['extra_len'] != 0) - { - fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']); - } - if ($p_header['comment_len'] != 0) - { - fputs($this->zip_fd, $p_header['comment'], $p_header['comment_len']); - } + // ----- Look for a directory + if (substr($p_options[PCLZIP_OPT_BY_NAME][$j], -1) == "/") { - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privWriteCentralHeader() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privWriteCentralHeader($p_nb_entries, $p_size, $p_offset, $p_comment) - { - $v_result=1; - - // ----- Packed data - $v_binary_data = pack("VvvvvVVv", 0x06054b50, 0, 0, $p_nb_entries, - $p_nb_entries, $p_size, - $p_offset, strlen($p_comment)); - - // ----- Write the 22 bytes of the header in the zip file - fputs($this->zip_fd, $v_binary_data, 22); - - // ----- Write the variable fields - if (strlen($p_comment) != 0) - { - fputs($this->zip_fd, $p_comment, strlen($p_comment)); - } + // ----- Look if the directory is in the filename path + if ((strlen($v_header['stored_filename']) > strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) && (substr($v_header['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) { + $v_extract = true; + } - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privList() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privList(&$p_list) - { - $v_result=1; - - // ----- Magic quotes trick - $this->privDisableMagicQuotes(); - - // ----- Open the zip file - if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0) - { - // ----- Magic quotes trick - $this->privSwapBackMagicQuotes(); + // ----- Look for a filename + } elseif ($v_header['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) { + $v_extract = true; + } + } - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in binary read mode'); + // ----- Look for extract by preg rule + } elseif ((isset($p_options[PCLZIP_OPT_BY_PREG])) && ($p_options[PCLZIP_OPT_BY_PREG] != "")) { - // ----- Return - return PclZip::errorCode(); - } + if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header['stored_filename'])) { + $v_extract = true; + } - // ----- Read the central directory informations - $v_central_dir = array(); - if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) - { - $this->privSwapBackMagicQuotes(); - return $v_result; - } + // ----- Look for extract by index rule + } elseif ((isset($p_options[PCLZIP_OPT_BY_INDEX])) && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) { - // ----- Go to beginning of Central Dir - @rewind($this->zip_fd); - if (@fseek($this->zip_fd, $v_central_dir['offset'])) - { - $this->privSwapBackMagicQuotes(); + // ----- Look if the index is in the list + for ($j = $j_start; ($j < sizeof($p_options[PCLZIP_OPT_BY_INDEX])) && (!$v_extract); $j++) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + if (($i >= $p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i <= $p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) { + $v_extract = true; + } + if ($i >= $p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) { + $j_start = $j + 1; + } - // ----- Return - return PclZip::errorCode(); - } + if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start'] > $i) { + break; + } + } - // ----- Read each entry - for ($i=0; $i<$v_central_dir['entries']; $i++) - { - // ----- Read the file header - if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1) - { - $this->privSwapBackMagicQuotes(); - return $v_result; - } - $v_header['index'] = $i; + // ----- Look for no rule, which means extract all the archive + } else { + $v_extract = true; + } - // ----- Get the only interesting attributes - $this->privConvertHeader2FileInfo($v_header, $p_list[$i]); - unset($v_header); - } + // ----- Check compression method + if (($v_extract) && (($v_header['compression'] != 8) && ($v_header['compression'] != 0))) { + $v_header['status'] = 'unsupported_compression'; - // ----- Close the zip file - $this->privCloseFd(); + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) { - // ----- Magic quotes trick - $this->privSwapBackMagicQuotes(); + $this->privSwapBackMagicQuotes(); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privConvertHeader2FileInfo() - // Description : - // This function takes the file informations from the central directory - // entries and extract the interesting parameters that will be given back. - // The resulting file infos are set in the array $p_info - // $p_info['filename'] : Filename with full path. Given by user (add), - // extracted in the filesystem (extract). - // $p_info['stored_filename'] : Stored filename in the archive. - // $p_info['size'] = Size of the file. - // $p_info['compressed_size'] = Compressed size of the file. - // $p_info['mtime'] = Last modification date of the file. - // $p_info['comment'] = Comment associated with the file. - // $p_info['folder'] = true/false : indicates if the entry is a folder or not. - // $p_info['status'] = status of the action on the file. - // $p_info['crc'] = CRC of the file content. - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privConvertHeader2FileInfo($p_header, &$p_info) - { - $v_result=1; - - // ----- Get the interesting attributes - $v_temp_path = PclZipUtilPathReduction($p_header['filename']); - $p_info['filename'] = $v_temp_path; - $v_temp_path = PclZipUtilPathReduction($p_header['stored_filename']); - $p_info['stored_filename'] = $v_temp_path; - $p_info['size'] = $p_header['size']; - $p_info['compressed_size'] = $p_header['compressed_size']; - $p_info['mtime'] = $p_header['mtime']; - $p_info['comment'] = $p_header['comment']; - $p_info['folder'] = (($p_header['external']&0x00000010)==0x00000010); - $p_info['index'] = $p_header['index']; - $p_info['status'] = $p_header['status']; - $p_info['crc'] = $p_header['crc']; + PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_COMPRESSION, "Filename '" . $v_header['stored_filename'] . "' is " . "compressed by an unsupported compression " . "method (" . $v_header['compression'] . ") "); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privExtractByRule() - // Description : - // Extract a file or directory depending of rules (by index, by name, ...) - // Parameters : - // $p_file_list : An array where will be placed the properties of each - // extracted file - // $p_path : Path to add while writing the extracted files - // $p_remove_path : Path to remove (from the file memorized path) while writing the - // extracted files. If the path does not match the file path, - // the file is extracted with its memorized path. - // $p_remove_path does not apply to 'list' mode. - // $p_path and $p_remove_path are commulative. - // Return Values : - // 1 on success,0 or less on error (see error code list) - // -------------------------------------------------------------------------------- - function privExtractByRule(&$p_file_list, $p_path, $p_remove_path, $p_remove_all_path, &$p_options) - { - $v_result=1; - - // ----- Magic quotes trick - $this->privDisableMagicQuotes(); - - // ----- Check the path - if ( ($p_path == "") - || ( (substr($p_path, 0, 1) != "/") - && (substr($p_path, 0, 3) != "../") - && (substr($p_path,1,2)!=":/"))) - $p_path = "./".$p_path; - - // ----- Reduce the path last (and duplicated) '/' - if (($p_path != "./") && ($p_path != "/")) - { - // ----- Look for the path end '/' - while (substr($p_path, -1) == "/") - { - $p_path = substr($p_path, 0, strlen($p_path)-1); - } - } + return PclZip::errorCode(); + } + } - // ----- Look for path to remove format (should end by /) - if (($p_remove_path != "") && (substr($p_remove_path, -1) != '/')) - { - $p_remove_path .= '/'; - } - $p_remove_path_size = strlen($p_remove_path); + // ----- Check encrypted files + if (($v_extract) && (($v_header['flag'] & 1) == 1)) { + $v_header['status'] = 'unsupported_encryption'; - // ----- Open the zip file - if (($v_result = $this->privOpenFd('rb')) != 1) - { - $this->privSwapBackMagicQuotes(); - return $v_result; - } + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) { - // ----- Read the central directory informations - $v_central_dir = array(); - if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) - { - // ----- Close the zip file - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); + $this->privSwapBackMagicQuotes(); - return $v_result; - } + PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, "Unsupported encryption for " . " filename '" . $v_header['stored_filename'] . "'"); - // ----- Start at beginning of Central Dir - $v_pos_entry = $v_central_dir['offset']; + return PclZip::errorCode(); + } + } - // ----- Read each entry - $j_start = 0; - for ($i=0, $v_nb_extracted=0; $i<$v_central_dir['entries']; $i++) - { + // ----- Look for real extraction + if (($v_extract) && ($v_header['status'] != 'ok')) { + $v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++]); + if ($v_result != 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); - // ----- Read next Central dir entry - @rewind($this->zip_fd); - if (@fseek($this->zip_fd, $v_pos_entry)) - { - // ----- Close the zip file - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); + return $v_result; + } - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + $v_extract = false; + } - // ----- Return - return PclZip::errorCode(); - } + // ----- Look for real extraction + if ($v_extract) { + + // ----- Go to the file position + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_header['offset'])) { + // ----- Close the zip file + $this->privCloseFd(); + + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for extraction as string + if ($p_options[PCLZIP_OPT_EXTRACT_AS_STRING]) { + + $v_string = ''; + + // ----- Extracting the file + $v_result1 = $this->privExtractFileAsString($v_header, $v_string, $p_options); + if ($v_result1 < 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result1; + } + + // ----- Get the only interesting attributes + if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Set the file content + $p_file_list[$v_nb_extracted]['content'] = $v_string; + + // ----- Next extracted file + $v_nb_extracted++; + + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } + + // ----- Look for extraction in standard output + } elseif ((isset($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) && ($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) { + // ----- Extracting the file in standard output + $v_result1 = $this->privExtractFileInOutput($v_header, $p_options); + if ($v_result1 < 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result1; + } + + // ----- Get the only interesting attributes + if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } + + // ----- Look for normal extraction + } else { + // ----- Extracting the file + $v_result1 = $this->privExtractFile($v_header, $p_path, $p_remove_path, $p_remove_all_path, $p_options); + if ($v_result1 < 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result1; + } + + // ----- Get the only interesting attributes + if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } + } + } + } - // ----- Read the file header - $v_header = array(); - if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1) - { // ----- Close the zip file $this->privCloseFd(); $this->privSwapBackMagicQuotes(); + // ----- Return return $v_result; - } - - // ----- Store the index - $v_header['index'] = $i; - - // ----- Store the file position - $v_pos_entry = ftell($this->zip_fd); - - // ----- Look for the specific extract rules - $v_extract = false; - - // ----- Look for extract by name rule - if ( (isset($p_options[PCLZIP_OPT_BY_NAME])) - && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) { - - // ----- Look if the filename is in the list - for ($j=0; ($j strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) - && (substr($v_header['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) { - $v_extract = true; - } - } - // ----- Look for a filename - elseif ($v_header['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) { - $v_extract = true; - } - } - } - - // ----- Look for extract by ereg rule - // ereg() is deprecated with PHP 5.3 - /* - else if ( (isset($p_options[PCLZIP_OPT_BY_EREG])) - && ($p_options[PCLZIP_OPT_BY_EREG] != "")) { - - if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header['stored_filename'])) { - $v_extract = true; - } - } - */ - - // ----- Look for extract by preg rule - else if ( (isset($p_options[PCLZIP_OPT_BY_PREG])) - && ($p_options[PCLZIP_OPT_BY_PREG] != "")) { - - if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header['stored_filename'])) { - $v_extract = true; - } - } - - // ----- Look for extract by index rule - else if ( (isset($p_options[PCLZIP_OPT_BY_INDEX])) - && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) { - - // ----- Look if the index is in the list - for ($j=$j_start; ($j=$p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i<=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) { - $v_extract = true; - } - if ($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) { - $j_start = $j+1; - } - - if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start']>$i) { - break; - } - } - } - - // ----- Look for no rule, which means extract all the archive - else { - $v_extract = true; - } - - // ----- Check compression method - if ( ($v_extract) - && ( ($v_header['compression'] != 8) - && ($v_header['compression'] != 0))) { - $v_header['status'] = 'unsupported_compression'; - - // ----- Look for PCLZIP_OPT_STOP_ON_ERROR - if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) - && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { - - $this->privSwapBackMagicQuotes(); - - PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_COMPRESSION, - "Filename '".$v_header['stored_filename']."' is " - ."compressed by an unsupported compression " - ."method (".$v_header['compression'].") "); - - return PclZip::errorCode(); - } - } - - // ----- Check encrypted files - if (($v_extract) && (($v_header['flag'] & 1) == 1)) { - $v_header['status'] = 'unsupported_encryption'; - - // ----- Look for PCLZIP_OPT_STOP_ON_ERROR - if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) - && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { - - $this->privSwapBackMagicQuotes(); - - PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, - "Unsupported encryption for " - ." filename '".$v_header['stored_filename'] - ."'"); - - return PclZip::errorCode(); - } } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractFile() + // Description : + // Parameters : + // Return Values : + // + // 1 : ... ? + // PCLZIP_ERR_USER_ABORTED(2) : User ask for extraction stop in callback + // -------------------------------------------------------------------------------- + public function privExtractFile(&$p_entry, $p_path, $p_remove_path, $p_remove_all_path, &$p_options) + { + $v_result = 1; + + // ----- Read the file header + if (($v_result = $this->privReadFileHeader($v_header)) != 1) { + // ----- Return + return $v_result; + } - // ----- Look for real extraction - if (($v_extract) && ($v_header['status'] != 'ok')) { - $v_result = $this->privConvertHeader2FileInfo($v_header, - $p_file_list[$v_nb_extracted++]); - if ($v_result != 1) { - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); - return $v_result; - } + // ----- Check that the file header is coherent with $p_entry info + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } - $v_extract = false; - } + // ----- Look for all path to remove + if ($p_remove_all_path == true) { + // ----- Look for folder entry that not need to be extracted + if (($p_entry['external'] & 0x00000010) == 0x00000010) { - // ----- Look for real extraction - if ($v_extract) - { + $p_entry['status'] = "filtered"; - // ----- Go to the file position - @rewind($this->zip_fd); - if (@fseek($this->zip_fd, $v_header['offset'])) - { - // ----- Close the zip file - $this->privCloseFd(); + return $v_result; + } - $this->privSwapBackMagicQuotes(); + // ----- Get the basename of the path + $p_entry['filename'] = basename($p_entry['filename']); - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + // ----- Look for path to remove + } elseif ($p_remove_path != "") { + if (PclZipUtilPathInclusion($p_remove_path, $p_entry['filename']) == 2) { - // ----- Return - return PclZip::errorCode(); - } + // ----- Change the file status + $p_entry['status'] = "filtered"; - // ----- Look for extraction as string - if ($p_options[PCLZIP_OPT_EXTRACT_AS_STRING]) { + // ----- Return + return $v_result; + } - $v_string = ''; + $p_remove_path_size = strlen($p_remove_path); + if (substr($p_entry['filename'], 0, $p_remove_path_size) == $p_remove_path) { - // ----- Extracting the file - $v_result1 = $this->privExtractFileAsString($v_header, $v_string, $p_options); - if ($v_result1 < 1) { - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); - return $v_result1; - } + // ----- Remove the path + $p_entry['filename'] = substr($p_entry['filename'], $p_remove_path_size); - // ----- Get the only interesting attributes - if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted])) != 1) - { - // ----- Close the zip file - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); + } + } - return $v_result; - } + // ----- Add the path + if ($p_path != '') { + $p_entry['filename'] = $p_path . "/" . $p_entry['filename']; + } - // ----- Set the file content - $p_file_list[$v_nb_extracted]['content'] = $v_string; + // ----- Check a base_dir_restriction + if (isset($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION])) { + $v_inclusion = PclZipUtilPathInclusion($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION], $p_entry['filename']); + if ($v_inclusion == 0) { - // ----- Next extracted file - $v_nb_extracted++; + PclZip::privErrorLog(PCLZIP_ERR_DIRECTORY_RESTRICTION, "Filename '" . $p_entry['filename'] . "' is " . "outside PCLZIP_OPT_EXTRACT_DIR_RESTRICTION"); - // ----- Look for user callback abort - if ($v_result1 == 2) { - break; - } + return PclZip::errorCode(); + } } - // ----- Look for extraction in standard output - elseif ( (isset($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) - && ($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) { - // ----- Extracting the file in standard output - $v_result1 = $this->privExtractFileInOutput($v_header, $p_options); - if ($v_result1 < 1) { - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); - return $v_result1; - } - // ----- Get the only interesting attributes - if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) { - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); - return $v_result; - } - - // ----- Look for user callback abort - if ($v_result1 == 2) { - break; - } - } - // ----- Look for normal extraction - else { - // ----- Extracting the file - $v_result1 = $this->privExtractFile($v_header, - $p_path, $p_remove_path, - $p_remove_all_path, - $p_options); - if ($v_result1 < 1) { - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); - return $v_result1; - } + // ----- Look for pre-extract callback + if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { - // ----- Get the only interesting attributes - if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) - { - // ----- Close the zip file - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_entry['status'] = "skipped"; + $v_result = 1; + } - return $v_result; - } + // ----- Look for abort result + if ($v_result == 2) { + // ----- This status is internal and will be changed in 'skipped' + $p_entry['status'] = "aborted"; + $v_result = PCLZIP_ERR_USER_ABORTED; + } - // ----- Look for user callback abort - if ($v_result1 == 2) { - break; - } + // ----- Update the informations + // Only some fields can be modified + $p_entry['filename'] = $v_local_header['filename']; } - } - } - - // ----- Close the zip file - $this->privCloseFd(); - $this->privSwapBackMagicQuotes(); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privExtractFile() - // Description : - // Parameters : - // Return Values : - // - // 1 : ... ? - // PCLZIP_ERR_USER_ABORTED(2) : User ask for extraction stop in callback - // -------------------------------------------------------------------------------- - function privExtractFile(&$p_entry, $p_path, $p_remove_path, $p_remove_all_path, &$p_options) - { - $v_result=1; - - // ----- Read the file header - if (($v_result = $this->privReadFileHeader($v_header)) != 1) - { - // ----- Return - return $v_result; - } + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { + // ----- Look for specific actions while the file exist + if (file_exists($p_entry['filename'])) { - // ----- Check that the file header is coherent with $p_entry info - if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { - // TBC - } + // ----- Look if file is a directory + if (is_dir($p_entry['filename'])) { - // ----- Look for all path to remove - if ($p_remove_all_path == true) { - // ----- Look for folder entry that not need to be extracted - if (($p_entry['external']&0x00000010)==0x00000010) { + // ----- Change the file status + $p_entry['status'] = "already_a_directory"; - $p_entry['status'] = "filtered"; + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) { - return $v_result; - } + PclZip::privErrorLog(PCLZIP_ERR_ALREADY_A_DIRECTORY, "Filename '" . $p_entry['filename'] . "' is " . "already used by an existing directory"); - // ----- Get the basename of the path - $p_entry['filename'] = basename($p_entry['filename']); - } + return PclZip::errorCode(); + } - // ----- Look for path to remove - else if ($p_remove_path != "") - { - if (PclZipUtilPathInclusion($p_remove_path, $p_entry['filename']) == 2) - { + // ----- Look if file is write protected + } elseif (!is_writeable($p_entry['filename'])) { - // ----- Change the file status - $p_entry['status'] = "filtered"; + // ----- Change the file status + $p_entry['status'] = "write_protected"; - // ----- Return - return $v_result; - } + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) { - $p_remove_path_size = strlen($p_remove_path); - if (substr($p_entry['filename'], 0, $p_remove_path_size) == $p_remove_path) - { + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, "Filename '" . $p_entry['filename'] . "' exists " . "and is write protected"); - // ----- Remove the path - $p_entry['filename'] = substr($p_entry['filename'], $p_remove_path_size); + return PclZip::errorCode(); + } - } - } + // ----- Look if the extracted file is older + } elseif (filemtime($p_entry['filename']) > $p_entry['mtime']) { + // ----- Change the file status + if ((isset($p_options[PCLZIP_OPT_REPLACE_NEWER])) && ($p_options[PCLZIP_OPT_REPLACE_NEWER] === true)) { + } else { + $p_entry['status'] = "newer_exist"; - // ----- Add the path - if ($p_path != '') { - $p_entry['filename'] = $p_path."/".$p_entry['filename']; - } + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) { - // ----- Check a base_dir_restriction - if (isset($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION])) { - $v_inclusion - = PclZipUtilPathInclusion($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION], - $p_entry['filename']); - if ($v_inclusion == 0) { + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, "Newer version of '" . $p_entry['filename'] . "' exists " . "and option PCLZIP_OPT_REPLACE_NEWER is not selected"); - PclZip::privErrorLog(PCLZIP_ERR_DIRECTORY_RESTRICTION, - "Filename '".$p_entry['filename']."' is " - ."outside PCLZIP_OPT_EXTRACT_DIR_RESTRICTION"); + return PclZip::errorCode(); + } + } + } else { + } - return PclZip::errorCode(); - } - } + // ----- Check the directory availability and create it if necessary + } else { + if ((($p_entry['external'] & 0x00000010) == 0x00000010) || (substr($p_entry['filename'], -1) == '/')) { + $v_dir_to_check = $p_entry['filename']; + } elseif (!strstr($p_entry['filename'], "/")) { + $v_dir_to_check = ""; + } else { + $v_dir_to_check = dirname($p_entry['filename']); + } - // ----- Look for pre-extract callback - if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { - - // ----- Generate a local information - $v_local_header = array(); - $this->privConvertHeader2FileInfo($p_entry, $v_local_header); - - // ----- Call the callback - // Here I do not use call_user_func() because I need to send a reference to the - // header. -// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); - $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); - if ($v_result == 0) { - // ----- Change the file status - $p_entry['status'] = "skipped"; - $v_result = 1; - } - - // ----- Look for abort result - if ($v_result == 2) { - // ----- This status is internal and will be changed in 'skipped' - $p_entry['status'] = "aborted"; - $v_result = PCLZIP_ERR_USER_ABORTED; - } - - // ----- Update the informations - // Only some fields can be modified - $p_entry['filename'] = $v_local_header['filename']; - } + if (($v_result = $this->privDirCheck($v_dir_to_check, (($p_entry['external'] & 0x00000010) == 0x00000010))) != 1) { + // ----- Change the file status + $p_entry['status'] = "path_creation_fail"; - // ----- Look if extraction should be done - if ($p_entry['status'] == 'ok') { + // ----- Return + //return $v_result; + $v_result = 1; + } + } + } - // ----- Look for specific actions while the file exist - if (file_exists($p_entry['filename'])) - { + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { - // ----- Look if file is a directory - if (is_dir($p_entry['filename'])) - { + // ----- Do the extraction (if not a folder) + if (!(($p_entry['external'] & 0x00000010) == 0x00000010)) { + // ----- Look for not compressed file + if ($p_entry['compression'] == 0) { - // ----- Change the file status - $p_entry['status'] = "already_a_directory"; + // ----- Opening destination file + if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { - // ----- Look for PCLZIP_OPT_STOP_ON_ERROR - // For historical reason first PclZip implementation does not stop - // when this kind of error occurs. - if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) - && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + // ----- Change the file status + $p_entry['status'] = "write_error"; - PclZip::privErrorLog(PCLZIP_ERR_ALREADY_A_DIRECTORY, - "Filename '".$p_entry['filename']."' is " - ."already used by an existing directory"); + // ----- Return + return $v_result; + } - return PclZip::errorCode(); - } - } - // ----- Look if file is write protected - else if (!is_writeable($p_entry['filename'])) - { + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = $p_entry['compressed_size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($this->zip_fd, $v_read_size); + /* Try to speed up the code + $v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($v_dest_file, $v_binary_data, $v_read_size); + */ + @fwrite($v_dest_file, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } - // ----- Change the file status - $p_entry['status'] = "write_protected"; + // ----- Closing the destination file + fclose($v_dest_file); - // ----- Look for PCLZIP_OPT_STOP_ON_ERROR - // For historical reason first PclZip implementation does not stop - // when this kind of error occurs. - if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) - && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + // ----- Change the file mtime + touch($p_entry['filename'], $p_entry['mtime']); - PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, - "Filename '".$p_entry['filename']."' exists " - ."and is write protected"); + } else { + // ----- TBC + // Need to be finished + if (($p_entry['flag'] & 1) == 1) { + PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, 'File \'' . $p_entry['filename'] . '\' is encrypted. Encrypted files are not supported.'); - return PclZip::errorCode(); - } - } - - // ----- Look if the extracted file is older - else if (filemtime($p_entry['filename']) > $p_entry['mtime']) - { - // ----- Change the file status - if ( (isset($p_options[PCLZIP_OPT_REPLACE_NEWER])) - && ($p_options[PCLZIP_OPT_REPLACE_NEWER]===true)) { - } - else { - $p_entry['status'] = "newer_exist"; - - // ----- Look for PCLZIP_OPT_STOP_ON_ERROR - // For historical reason first PclZip implementation does not stop - // when this kind of error occurs. - if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) - && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { - - PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, - "Newer version of '".$p_entry['filename']."' exists " - ."and option PCLZIP_OPT_REPLACE_NEWER is not selected"); + return PclZip::errorCode(); + } - return PclZip::errorCode(); - } - } - } - else { - } - } + // ----- Look for using temporary file to unzip + if ((!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON]) || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_entry['size'])))) { + $v_result = $this->privExtractFileUsingTempFile($p_entry, $p_options); + if ($v_result < PCLZIP_ERR_NO_ERROR) { + return $v_result; + } - // ----- Check the directory availability and create it if necessary - else { - if ((($p_entry['external']&0x00000010)==0x00000010) || (substr($p_entry['filename'], -1) == '/')) - $v_dir_to_check = $p_entry['filename']; - else if (!strstr($p_entry['filename'], "/")) - $v_dir_to_check = ""; - else - $v_dir_to_check = dirname($p_entry['filename']); + // ----- Look for extract in memory + } else { - if (($v_result = $this->privDirCheck($v_dir_to_check, (($p_entry['external']&0x00000010)==0x00000010))) != 1) { + // ----- Read the compressed file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); - // ----- Change the file status - $p_entry['status'] = "path_creation_fail"; + // ----- Decompress the file + $v_file_content = @gzinflate($v_buffer); + unset($v_buffer); + if ($v_file_content === false) { - // ----- Return - //return $v_result; - $v_result = 1; - } - } - } + // ----- Change the file status + // TBC + $p_entry['status'] = "error"; - // ----- Look if extraction should be done - if ($p_entry['status'] == 'ok') { + return $v_result; + } - // ----- Do the extraction (if not a folder) - if (!(($p_entry['external']&0x00000010)==0x00000010)) - { - // ----- Look for not compressed file - if ($p_entry['compression'] == 0) { + // ----- Opening destination file + if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { - // ----- Opening destination file - if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) - { + // ----- Change the file status + $p_entry['status'] = "write_error"; - // ----- Change the file status - $p_entry['status'] = "write_error"; + return $v_result; + } - // ----- Return - return $v_result; - } + // ----- Write the uncompressed data + @fwrite($v_dest_file, $v_file_content, $p_entry['size']); + unset($v_file_content); + // ----- Closing the destination file + @fclose($v_dest_file); - // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks - $v_size = $p_entry['compressed_size']; - while ($v_size != 0) - { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @fread($this->zip_fd, $v_read_size); - /* Try to speed up the code - $v_binary_data = pack('a'.$v_read_size, $v_buffer); - @fwrite($v_dest_file, $v_binary_data, $v_read_size); - */ - @fwrite($v_dest_file, $v_buffer, $v_read_size); - $v_size -= $v_read_size; - } + } - // ----- Closing the destination file - fclose($v_dest_file); + // ----- Change the file mtime + @touch($p_entry['filename'], $p_entry['mtime']); + } - // ----- Change the file mtime - touch($p_entry['filename'], $p_entry['mtime']); + // ----- Look for chmod option + if (isset($p_options[PCLZIP_OPT_SET_CHMOD])) { + // ----- Change the mode of the file + @chmod($p_entry['filename'], $p_options[PCLZIP_OPT_SET_CHMOD]); + } + } } - else { - // ----- TBC - // Need to be finished - if (($p_entry['flag'] & 1) == 1) { - PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, 'File \''.$p_entry['filename'].'\' is encrypted. Encrypted files are not supported.'); - return PclZip::errorCode(); - } + // ----- Change abort status + if ($p_entry['status'] == "aborted") { + $p_entry['status'] = "skipped"; - // ----- Look for using temporary file to unzip - if ( (!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) - && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON]) - || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) - && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_entry['size'])) ) ) { - $v_result = $this->privExtractFileUsingTempFile($p_entry, $p_options); - if ($v_result < PCLZIP_ERR_NO_ERROR) { - return $v_result; - } - } + // ----- Look for post-extract callback + } elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { - // ----- Look for extract in memory - else { + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); - // ----- Read the compressed file in a buffer (one shot) - $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + // ----- Look for abort result + if ($v_result == 2) { + $v_result = PCLZIP_ERR_USER_ABORTED; + } + } - // ----- Decompress the file - $v_file_content = @gzinflate($v_buffer); - unset($v_buffer); - if ($v_file_content === FALSE) { + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- - // ----- Change the file status - // TBC - $p_entry['status'] = "error"; + // -------------------------------------------------------------------------------- + // Function : privExtractFileUsingTempFile() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privExtractFileUsingTempFile(&$p_entry, &$p_options) + { + $v_result = 1; - return $v_result; - } + // ----- Creates a temporary file + $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.gz'; + if (($v_dest_file = @fopen($v_gzip_temp_name, "wb")) == 0) { + fclose($v_file); + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary write mode'); - // ----- Opening destination file - if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { + return PclZip::errorCode(); + } - // ----- Change the file status - $p_entry['status'] = "write_error"; + // ----- Write gz file format header + $v_binary_data = pack('va1a1Va1a1', 0x8b1f, chr($p_entry['compression']), chr(0x00), time(), chr(0x00), chr(3)); + @fwrite($v_dest_file, $v_binary_data, 10); - return $v_result; - } + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = $p_entry['compressed_size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($this->zip_fd, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($v_dest_file, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } - // ----- Write the uncompressed data - @fwrite($v_dest_file, $v_file_content, $p_entry['size']); - unset($v_file_content); + // ----- Write gz file format footer + $v_binary_data = pack('VV', $p_entry['crc'], $p_entry['size']); + @fwrite($v_dest_file, $v_binary_data, 8); - // ----- Closing the destination file - @fclose($v_dest_file); + // ----- Close the temporary file + @fclose($v_dest_file); - } + // ----- Opening destination file + if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { + $p_entry['status'] = "write_error"; - // ----- Change the file mtime - @touch($p_entry['filename'], $p_entry['mtime']); + return $v_result; } - // ----- Look for chmod option - if (isset($p_options[PCLZIP_OPT_SET_CHMOD])) { + // ----- Open the temporary gz file + if (($v_src_file = @gzopen($v_gzip_temp_name, 'rb')) == 0) { + @fclose($v_dest_file); + $p_entry['status'] = "read_error"; + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary read mode'); - // ----- Change the mode of the file - @chmod($p_entry['filename'], $p_options[PCLZIP_OPT_SET_CHMOD]); + return PclZip::errorCode(); } - } - } + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = $p_entry['size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @gzread($v_src_file, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($v_dest_file, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + @fclose($v_dest_file); + @gzclose($v_src_file); - // ----- Change abort status - if ($p_entry['status'] == "aborted") { - $p_entry['status'] = "skipped"; + // ----- Delete the temporary file + @unlink($v_gzip_temp_name); + + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractFileInOutput() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privExtractFileInOutput(&$p_entry, &$p_options) + { + $v_result = 1; - // ----- Look for post-extract callback - elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { + // ----- Read the file header + if (($v_result = $this->privReadFileHeader($v_header)) != 1) { + return $v_result; + } - // ----- Generate a local information - $v_local_header = array(); - $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + // ----- Check that the file header is coherent with $p_entry info + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } - // ----- Call the callback - // Here I do not use call_user_func() because I need to send a reference to the - // header. -// eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); - $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); + // ----- Look for pre-extract callback + if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { - // ----- Look for abort result - if ($v_result == 2) { - $v_result = PCLZIP_ERR_USER_ABORTED; - } - } + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_entry['status'] = "skipped"; + $v_result = 1; + } - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privExtractFileUsingTempFile() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privExtractFileUsingTempFile(&$p_entry, &$p_options) - { - $v_result=1; - - // ----- Creates a temporary file - $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.gz'; - if (($v_dest_file = @fopen($v_gzip_temp_name, "wb")) == 0) { - fclose($v_file); - PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary write mode'); - return PclZip::errorCode(); - } + // ----- Look for abort result + if ($v_result == 2) { + // ----- This status is internal and will be changed in 'skipped' + $p_entry['status'] = "aborted"; + $v_result = PCLZIP_ERR_USER_ABORTED; + } + + // ----- Update the informations + // Only some fields can be modified + $p_entry['filename'] = $v_local_header['filename']; + } + + // ----- Trace + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { - // ----- Write gz file format header - $v_binary_data = pack('va1a1Va1a1', 0x8b1f, Chr($p_entry['compression']), Chr(0x00), time(), Chr(0x00), Chr(3)); - @fwrite($v_dest_file, $v_binary_data, 10); + // ----- Do the extraction (if not a folder) + if (!(($p_entry['external'] & 0x00000010) == 0x00000010)) { + // ----- Look for not compressed file + if ($p_entry['compressed_size'] == $p_entry['size']) { - // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks - $v_size = $p_entry['compressed_size']; - while ($v_size != 0) - { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @fread($this->zip_fd, $v_read_size); - //$v_binary_data = pack('a'.$v_read_size, $v_buffer); - @fwrite($v_dest_file, $v_buffer, $v_read_size); - $v_size -= $v_read_size; - } + // ----- Read the file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); - // ----- Write gz file format footer - $v_binary_data = pack('VV', $p_entry['crc'], $p_entry['size']); - @fwrite($v_dest_file, $v_binary_data, 8); + // ----- Send the file to the output + echo $v_buffer; + unset($v_buffer); + } else { - // ----- Close the temporary file - @fclose($v_dest_file); + // ----- Read the compressed file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); - // ----- Opening destination file - if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { - $p_entry['status'] = "write_error"; - return $v_result; - } + // ----- Decompress the file + $v_file_content = gzinflate($v_buffer); + unset($v_buffer); - // ----- Open the temporary gz file - if (($v_src_file = @gzopen($v_gzip_temp_name, 'rb')) == 0) { - @fclose($v_dest_file); - $p_entry['status'] = "read_error"; - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode'); - return PclZip::errorCode(); - } + // ----- Send the file to the output + echo $v_file_content; + unset($v_file_content); + } + } + } + // ----- Change abort status + if ($p_entry['status'] == "aborted") { + $p_entry['status'] = "skipped"; - // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks - $v_size = $p_entry['size']; - while ($v_size != 0) { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @gzread($v_src_file, $v_read_size); - //$v_binary_data = pack('a'.$v_read_size, $v_buffer); - @fwrite($v_dest_file, $v_buffer, $v_read_size); - $v_size -= $v_read_size; - } - @fclose($v_dest_file); - @gzclose($v_src_file); + // ----- Look for post-extract callback + } elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { - // ----- Delete the temporary file - @unlink($v_gzip_temp_name); + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privExtractFileInOutput() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privExtractFileInOutput(&$p_entry, &$p_options) - { - $v_result=1; - - // ----- Read the file header - if (($v_result = $this->privReadFileHeader($v_header)) != 1) { - return $v_result; - } + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); + // ----- Look for abort result + if ($v_result == 2) { + $v_result = PCLZIP_ERR_USER_ABORTED; + } + } - // ----- Check that the file header is coherent with $p_entry info - if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { - // TBC + return $v_result; } + // -------------------------------------------------------------------------------- - // ----- Look for pre-extract callback - if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { - - // ----- Generate a local information - $v_local_header = array(); - $this->privConvertHeader2FileInfo($p_entry, $v_local_header); - - // ----- Call the callback - // Here I do not use call_user_func() because I need to send a reference to the - // header. -// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); - $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); - if ($v_result == 0) { - // ----- Change the file status - $p_entry['status'] = "skipped"; + // -------------------------------------------------------------------------------- + // Function : privExtractFileAsString() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privExtractFileAsString(&$p_entry, &$p_string, &$p_options) + { $v_result = 1; - } - - // ----- Look for abort result - if ($v_result == 2) { - // ----- This status is internal and will be changed in 'skipped' - $p_entry['status'] = "aborted"; - $v_result = PCLZIP_ERR_USER_ABORTED; - } - - // ----- Update the informations - // Only some fields can be modified - $p_entry['filename'] = $v_local_header['filename']; - } - // ----- Trace + // ----- Read the file header + $v_header = array(); + if (($v_result = $this->privReadFileHeader($v_header)) != 1) { + // ----- Return + return $v_result; + } + + // ----- Check that the file header is coherent with $p_entry info + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } - // ----- Look if extraction should be done - if ($p_entry['status'] == 'ok') { + // ----- Look for pre-extract callback + if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { - // ----- Do the extraction (if not a folder) - if (!(($p_entry['external']&0x00000010)==0x00000010)) { - // ----- Look for not compressed file - if ($p_entry['compressed_size'] == $p_entry['size']) { + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_entry['status'] = "skipped"; + $v_result = 1; + } - // ----- Read the file in a buffer (one shot) - $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + // ----- Look for abort result + if ($v_result == 2) { + // ----- This status is internal and will be changed in 'skipped' + $p_entry['status'] = "aborted"; + $v_result = PCLZIP_ERR_USER_ABORTED; + } - // ----- Send the file to the output - echo $v_buffer; - unset($v_buffer); + // ----- Update the informations + // Only some fields can be modified + $p_entry['filename'] = $v_local_header['filename']; } - else { - // ----- Read the compressed file in a buffer (one shot) - $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { + + // ----- Do the extraction (if not a folder) + if (!(($p_entry['external'] & 0x00000010) == 0x00000010)) { + // ----- Look for not compressed file + // if ($p_entry['compressed_size'] == $p_entry['size']) + if ($p_entry['compression'] == 0) { + + // ----- Reading the file + $p_string = @fread($this->zip_fd, $p_entry['compressed_size']); + } else { + + // ----- Reading the file + $v_data = @fread($this->zip_fd, $p_entry['compressed_size']); - // ----- Decompress the file - $v_file_content = gzinflate($v_buffer); - unset($v_buffer); + // ----- Decompress the file + if (($p_string = @gzinflate($v_data)) === false) { + // TBC + } + } + + // ----- Trace + } else { + // TBC : error : can not extract a folder in a string + } - // ----- Send the file to the output - echo $v_file_content; - unset($v_file_content); } - } - } - // ----- Change abort status - if ($p_entry['status'] == "aborted") { - $p_entry['status'] = "skipped"; - } + // ----- Change abort status + if ($p_entry['status'] == "aborted") { + $p_entry['status'] = "skipped"; - // ----- Look for post-extract callback - elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { + // ----- Look for post-extract callback + } elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { - // ----- Generate a local information - $v_local_header = array(); - $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); - // ----- Call the callback - // Here I do not use call_user_func() because I need to send a reference to the - // header. -// eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); - $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); + // ----- Swap the content to header + $v_local_header['content'] = $p_string; + $p_string = ''; - // ----- Look for abort result - if ($v_result == 2) { - $v_result = PCLZIP_ERR_USER_ABORTED; - } - } + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privExtractFileAsString() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privExtractFileAsString(&$p_entry, &$p_string, &$p_options) - { - $v_result=1; - - // ----- Read the file header - $v_header = array(); - if (($v_result = $this->privReadFileHeader($v_header)) != 1) - { - // ----- Return - return $v_result; - } + // ----- Swap back the content to header + $p_string = $v_local_header['content']; + unset($v_local_header['content']); + // ----- Look for abort result + if ($v_result == 2) { + $v_result = PCLZIP_ERR_USER_ABORTED; + } + } - // ----- Check that the file header is coherent with $p_entry info - if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { - // TBC + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- - // ----- Look for pre-extract callback - if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { - - // ----- Generate a local information - $v_local_header = array(); - $this->privConvertHeader2FileInfo($p_entry, $v_local_header); - - // ----- Call the callback - // Here I do not use call_user_func() because I need to send a reference to the - // header. -// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); - $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); - if ($v_result == 0) { - // ----- Change the file status - $p_entry['status'] = "skipped"; + // -------------------------------------------------------------------------------- + // Function : privReadFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privReadFileHeader(&$p_header) + { $v_result = 1; - } - - // ----- Look for abort result - if ($v_result == 2) { - // ----- This status is internal and will be changed in 'skipped' - $p_entry['status'] = "aborted"; - $v_result = PCLZIP_ERR_USER_ABORTED; - } - - // ----- Update the informations - // Only some fields can be modified - $p_entry['filename'] = $v_local_header['filename']; - } + // ----- Read the 4 bytes signature + $v_binary_data = @fread($this->zip_fd, 4); + $v_data = unpack('Vid', $v_binary_data); - // ----- Look if extraction should be done - if ($p_entry['status'] == 'ok') { + // ----- Check signature + if ($v_data['id'] != 0x04034b50) { - // ----- Do the extraction (if not a folder) - if (!(($p_entry['external']&0x00000010)==0x00000010)) { - // ----- Look for not compressed file - // if ($p_entry['compressed_size'] == $p_entry['size']) - if ($p_entry['compression'] == 0) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure'); - // ----- Reading the file - $p_string = @fread($this->zip_fd, $p_entry['compressed_size']); + // ----- Return + return PclZip::errorCode(); } - else { - // ----- Reading the file - $v_data = @fread($this->zip_fd, $p_entry['compressed_size']); + // ----- Read the first 42 bytes of the header + $v_binary_data = fread($this->zip_fd, 26); - // ----- Decompress the file - if (($p_string = @gzinflate($v_data)) === FALSE) { - // TBC - } - } + // ----- Look for invalid block size + if (strlen($v_binary_data) != 26) { + $p_header['filename'] = ""; + $p_header['status'] = "invalid_header"; - // ----- Trace - } - else { - // TBC : error : can not extract a folder in a string - } + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : " . strlen($v_binary_data)); - } + // ----- Return + return PclZip::errorCode(); + } - // ----- Change abort status - if ($p_entry['status'] == "aborted") { - $p_entry['status'] = "skipped"; - } + // ----- Extract the values + $v_data = unpack('vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $v_binary_data); + + // ----- Get filename + $p_header['filename'] = fread($this->zip_fd, $v_data['filename_len']); - // ----- Look for post-extract callback - elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { + // ----- Get extra_fields + if ($v_data['extra_len'] != 0) { + $p_header['extra'] = fread($this->zip_fd, $v_data['extra_len']); + } else { + $p_header['extra'] = ''; + } - // ----- Generate a local information - $v_local_header = array(); - $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + // ----- Extract properties + $p_header['version_extracted'] = $v_data['version']; + $p_header['compression'] = $v_data['compression']; + $p_header['size'] = $v_data['size']; + $p_header['compressed_size'] = $v_data['compressed_size']; + $p_header['crc'] = $v_data['crc']; + $p_header['flag'] = $v_data['flag']; + $p_header['filename_len'] = $v_data['filename_len']; + + // ----- Recuperate date in UNIX format + $p_header['mdate'] = $v_data['mdate']; + $p_header['mtime'] = $v_data['mtime']; + if ($p_header['mdate'] && $p_header['mtime']) { + // ----- Extract time + $v_hour = ($p_header['mtime'] & 0xF800) >> 11; + $v_minute = ($p_header['mtime'] & 0x07E0) >> 5; + $v_seconde = ($p_header['mtime'] & 0x001F) * 2; + + // ----- Extract date + $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980; + $v_month = ($p_header['mdate'] & 0x01E0) >> 5; + $v_day = $p_header['mdate'] & 0x001F; + + // ----- Get UNIX date format + $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year); + + } else { + $p_header['mtime'] = time(); + } - // ----- Swap the content to header - $v_local_header['content'] = $p_string; - $p_string = ''; + // TBC + //for (reset($v_data); $key = key($v_data); next($v_data)) { + //} - // ----- Call the callback - // Here I do not use call_user_func() because I need to send a reference to the - // header. -// eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); - $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); + // ----- Set the stored filename + $p_header['stored_filename'] = $p_header['filename']; - // ----- Swap back the content to header - $p_string = $v_local_header['content']; - unset($v_local_header['content']); + // ----- Set the status field + $p_header['status'] = "ok"; - // ----- Look for abort result - if ($v_result == 2) { - $v_result = PCLZIP_ERR_USER_ABORTED; - } + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privReadFileHeader() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privReadFileHeader(&$p_header) - { - $v_result=1; - - // ----- Read the 4 bytes signature - $v_binary_data = @fread($this->zip_fd, 4); - $v_data = unpack('Vid', $v_binary_data); - - // ----- Check signature - if ($v_data['id'] != 0x04034b50) + // -------------------------------------------------------------------------------- + // Function : privReadCentralFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privReadCentralFileHeader(&$p_header) { + $v_result = 1; - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure'); + // ----- Read the 4 bytes signature + $v_binary_data = @fread($this->zip_fd, 4); + $v_data = unpack('Vid', $v_binary_data); - // ----- Return - return PclZip::errorCode(); - } + // ----- Check signature + if ($v_data['id'] != 0x02014b50) { - // ----- Read the first 42 bytes of the header - $v_binary_data = fread($this->zip_fd, 26); + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure'); - // ----- Look for invalid block size - if (strlen($v_binary_data) != 26) - { - $p_header['filename'] = ""; - $p_header['status'] = "invalid_header"; + // ----- Return + return PclZip::errorCode(); + } - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data)); + // ----- Read the first 42 bytes of the header + $v_binary_data = fread($this->zip_fd, 42); - // ----- Return - return PclZip::errorCode(); - } + // ----- Look for invalid block size + if (strlen($v_binary_data) != 42) { + $p_header['filename'] = ""; + $p_header['status'] = "invalid_header"; - // ----- Extract the values - $v_data = unpack('vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $v_binary_data); + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : " . strlen($v_binary_data)); - // ----- Get filename - $p_header['filename'] = fread($this->zip_fd, $v_data['filename_len']); + // ----- Return + return PclZip::errorCode(); + } - // ----- Get extra_fields - if ($v_data['extra_len'] != 0) { - $p_header['extra'] = fread($this->zip_fd, $v_data['extra_len']); - } - else { - $p_header['extra'] = ''; - } + // ----- Extract the values + $p_header = unpack('vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $v_binary_data); - // ----- Extract properties - $p_header['version_extracted'] = $v_data['version']; - $p_header['compression'] = $v_data['compression']; - $p_header['size'] = $v_data['size']; - $p_header['compressed_size'] = $v_data['compressed_size']; - $p_header['crc'] = $v_data['crc']; - $p_header['flag'] = $v_data['flag']; - $p_header['filename_len'] = $v_data['filename_len']; - - // ----- Recuperate date in UNIX format - $p_header['mdate'] = $v_data['mdate']; - $p_header['mtime'] = $v_data['mtime']; - if ($p_header['mdate'] && $p_header['mtime']) - { - // ----- Extract time - $v_hour = ($p_header['mtime'] & 0xF800) >> 11; - $v_minute = ($p_header['mtime'] & 0x07E0) >> 5; - $v_seconde = ($p_header['mtime'] & 0x001F)*2; + // ----- Get filename + if ($p_header['filename_len'] != 0) { + $p_header['filename'] = fread($this->zip_fd, $p_header['filename_len']); + } else { + $p_header['filename'] = ''; + } - // ----- Extract date - $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980; - $v_month = ($p_header['mdate'] & 0x01E0) >> 5; - $v_day = $p_header['mdate'] & 0x001F; + // ----- Get extra + if ($p_header['extra_len'] != 0) { + $p_header['extra'] = fread($this->zip_fd, $p_header['extra_len']); + } else { + $p_header['extra'] = ''; + } - // ----- Get UNIX date format - $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year); + // ----- Get comment + if ($p_header['comment_len'] != 0) { + $p_header['comment'] = fread($this->zip_fd, $p_header['comment_len']); + } else { + $p_header['comment'] = ''; + } - } - else - { - $p_header['mtime'] = time(); - } + // ----- Extract properties - // TBC - //for(reset($v_data); $key = key($v_data); next($v_data)) { - //} + // ----- Recuperate date in UNIX format + //if ($p_header['mdate'] && $p_header['mtime']) + // TBC : bug : this was ignoring time with 0/0/0 + if (1) { + // ----- Extract time + $v_hour = ($p_header['mtime'] & 0xF800) >> 11; + $v_minute = ($p_header['mtime'] & 0x07E0) >> 5; + $v_seconde = ($p_header['mtime'] & 0x001F) * 2; - // ----- Set the stored filename - $p_header['stored_filename'] = $p_header['filename']; + // ----- Extract date + $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980; + $v_month = ($p_header['mdate'] & 0x01E0) >> 5; + $v_day = $p_header['mdate'] & 0x001F; - // ----- Set the status field - $p_header['status'] = "ok"; + // ----- Get UNIX date format + $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privReadCentralFileHeader() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privReadCentralFileHeader(&$p_header) - { - $v_result=1; - - // ----- Read the 4 bytes signature - $v_binary_data = @fread($this->zip_fd, 4); - $v_data = unpack('Vid', $v_binary_data); - - // ----- Check signature - if ($v_data['id'] != 0x02014b50) - { + } else { + $p_header['mtime'] = time(); + } - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure'); + // ----- Set the stored filename + $p_header['stored_filename'] = $p_header['filename']; - // ----- Return - return PclZip::errorCode(); - } + // ----- Set default status to ok + $p_header['status'] = 'ok'; - // ----- Read the first 42 bytes of the header - $v_binary_data = fread($this->zip_fd, 42); + // ----- Look if it is a directory + if (substr($p_header['filename'], -1) == '/') { + //$p_header['external'] = 0x41FF0010; + $p_header['external'] = 0x00000010; + } - // ----- Look for invalid block size - if (strlen($v_binary_data) != 42) + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCheckFileHeaders() + // Description : + // Parameters : + // Return Values : + // 1 on success, + // 0 on error; + // -------------------------------------------------------------------------------- + public function privCheckFileHeaders(&$p_local_header, &$p_central_header) { - $p_header['filename'] = ""; - $p_header['status'] = "invalid_header"; + $v_result = 1; + + // ----- Check the static values + // TBC + if ($p_local_header['filename'] != $p_central_header['filename']) { + } + if ($p_local_header['version_extracted'] != $p_central_header['version_extracted']) { + } + if ($p_local_header['flag'] != $p_central_header['flag']) { + } + if ($p_local_header['compression'] != $p_central_header['compression']) { + } + if ($p_local_header['mtime'] != $p_central_header['mtime']) { + } + if ($p_local_header['filename_len'] != $p_central_header['filename_len']) { + } - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data)); + // ----- Look for flag bit 3 + if (($p_local_header['flag'] & 8) == 8) { + $p_local_header['size'] = $p_central_header['size']; + $p_local_header['compressed_size'] = $p_central_header['compressed_size']; + $p_local_header['crc'] = $p_central_header['crc']; + } - // ----- Return - return PclZip::errorCode(); + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- - // ----- Extract the values - $p_header = unpack('vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $v_binary_data); - - // ----- Get filename - if ($p_header['filename_len'] != 0) - $p_header['filename'] = fread($this->zip_fd, $p_header['filename_len']); - else - $p_header['filename'] = ''; - - // ----- Get extra - if ($p_header['extra_len'] != 0) - $p_header['extra'] = fread($this->zip_fd, $p_header['extra_len']); - else - $p_header['extra'] = ''; - - // ----- Get comment - if ($p_header['comment_len'] != 0) - $p_header['comment'] = fread($this->zip_fd, $p_header['comment_len']); - else - $p_header['comment'] = ''; - - // ----- Extract properties - - // ----- Recuperate date in UNIX format - //if ($p_header['mdate'] && $p_header['mtime']) - // TBC : bug : this was ignoring time with 0/0/0 - if (1) + // -------------------------------------------------------------------------------- + // Function : privReadEndCentralDir() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privReadEndCentralDir(&$p_central_dir) { - // ----- Extract time - $v_hour = ($p_header['mtime'] & 0xF800) >> 11; - $v_minute = ($p_header['mtime'] & 0x07E0) >> 5; - $v_seconde = ($p_header['mtime'] & 0x001F)*2; + $v_result = 1; - // ----- Extract date - $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980; - $v_month = ($p_header['mdate'] & 0x01E0) >> 5; - $v_day = $p_header['mdate'] & 0x001F; + // ----- Go to the end of the zip file + $v_size = filesize($this->zipname); + @fseek($this->zip_fd, $v_size); + if (@ftell($this->zip_fd) != $v_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to go to the end of the archive \'' . $this->zipname . '\''); - // ----- Get UNIX date format - $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year); + // ----- Return + return PclZip::errorCode(); + } - } - else - { - $p_header['mtime'] = time(); - } + // ----- First try : look if this is an archive with no commentaries (most of the time) + // in this case the end of central dir is at 22 bytes of the file end + $v_found = 0; + if ($v_size > 26) { + @fseek($this->zip_fd, $v_size - 22); + if (($v_pos = @ftell($this->zip_fd)) != ($v_size - 22)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \'' . $this->zipname . '\''); - // ----- Set the stored filename - $p_header['stored_filename'] = $p_header['filename']; + // ----- Return + return PclZip::errorCode(); + } - // ----- Set default status to ok - $p_header['status'] = 'ok'; + // ----- Read for bytes + $v_binary_data = @fread($this->zip_fd, 4); + $v_data = @unpack('Vid', $v_binary_data); - // ----- Look if it is a directory - if (substr($p_header['filename'], -1) == '/') { - //$p_header['external'] = 0x41FF0010; - $p_header['external'] = 0x00000010; - } + // ----- Check signature + if ($v_data['id'] == 0x06054b50) { + $v_found = 1; + } + $v_pos = ftell($this->zip_fd); + } - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privCheckFileHeaders() - // Description : - // Parameters : - // Return Values : - // 1 on success, - // 0 on error; - // -------------------------------------------------------------------------------- - function privCheckFileHeaders(&$p_local_header, &$p_central_header) - { - $v_result=1; - - // ----- Check the static values - // TBC - if ($p_local_header['filename'] != $p_central_header['filename']) { - } - if ($p_local_header['version_extracted'] != $p_central_header['version_extracted']) { - } - if ($p_local_header['flag'] != $p_central_header['flag']) { - } - if ($p_local_header['compression'] != $p_central_header['compression']) { - } - if ($p_local_header['mtime'] != $p_central_header['mtime']) { - } - if ($p_local_header['filename_len'] != $p_central_header['filename_len']) { - } + // ----- Go back to the maximum possible size of the Central Dir End Record + if (!$v_found) { + $v_maximum_size = 65557; // 0xFFFF + 22; + if ($v_maximum_size > $v_size) { + $v_maximum_size = $v_size; + } + @fseek($this->zip_fd, $v_size - $v_maximum_size); + if (@ftell($this->zip_fd) != ($v_size - $v_maximum_size)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \'' . $this->zipname . '\''); - // ----- Look for flag bit 3 - if (($p_local_header['flag'] & 8) == 8) { - $p_local_header['size'] = $p_central_header['size']; - $p_local_header['compressed_size'] = $p_central_header['compressed_size']; - $p_local_header['crc'] = $p_central_header['crc']; - } + // ----- Return + return PclZip::errorCode(); + } - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privReadEndCentralDir() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privReadEndCentralDir(&$p_central_dir) - { - $v_result=1; - - // ----- Go to the end of the zip file - $v_size = filesize($this->zipname); - @fseek($this->zip_fd, $v_size); - if (@ftell($this->zip_fd) != $v_size) - { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to go to the end of the archive \''.$this->zipname.'\''); + // ----- Read byte per byte in order to find the signature + $v_pos = ftell($this->zip_fd); + $v_bytes = 0x00000000; + while ($v_pos < $v_size) { + // ----- Read a byte + $v_byte = @fread($this->zip_fd, 1); + + // ----- Add the byte + //$v_bytes = ($v_bytes << 8) | Ord($v_byte); + // Note we mask the old value down such that once shifted we can never end up with more than a 32bit number + // Otherwise on systems where we have 64bit integers the check below for the magic number will fail. + $v_bytes = (($v_bytes & 0xFFFFFF) << 8) | ord($v_byte); + + // ----- Compare the bytes + if ($v_bytes == 0x504b0506) { + $v_pos++; + break; + } + + $v_pos++; + } - // ----- Return - return PclZip::errorCode(); - } + // ----- Look if not found end of central dir + if ($v_pos == $v_size) { - // ----- First try : look if this is an archive with no commentaries (most of the time) - // in this case the end of central dir is at 22 bytes of the file end - $v_found = 0; - if ($v_size > 26) { - @fseek($this->zip_fd, $v_size-22); - if (($v_pos = @ftell($this->zip_fd)) != ($v_size-22)) - { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \''.$this->zipname.'\''); + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Unable to find End of Central Dir Record signature"); - // ----- Return - return PclZip::errorCode(); - } + // ----- Return + return PclZip::errorCode(); + } + } - // ----- Read for bytes - $v_binary_data = @fread($this->zip_fd, 4); - $v_data = @unpack('Vid', $v_binary_data); + // ----- Read the first 18 bytes of the header + $v_binary_data = fread($this->zip_fd, 18); - // ----- Check signature - if ($v_data['id'] == 0x06054b50) { - $v_found = 1; - } + // ----- Look for invalid block size + if (strlen($v_binary_data) != 18) { - $v_pos = ftell($this->zip_fd); - } + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid End of Central Dir Record size : " . strlen($v_binary_data)); - // ----- Go back to the maximum possible size of the Central Dir End Record - if (!$v_found) { - $v_maximum_size = 65557; // 0xFFFF + 22; - if ($v_maximum_size > $v_size) - $v_maximum_size = $v_size; - @fseek($this->zip_fd, $v_size-$v_maximum_size); - if (@ftell($this->zip_fd) != ($v_size-$v_maximum_size)) - { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \''.$this->zipname.'\''); + // ----- Return + return PclZip::errorCode(); + } - // ----- Return - return PclZip::errorCode(); - } + // ----- Extract the values + $v_data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', $v_binary_data); - // ----- Read byte per byte in order to find the signature - $v_pos = ftell($this->zip_fd); - $v_bytes = 0x00000000; - while ($v_pos < $v_size) - { - // ----- Read a byte - $v_byte = @fread($this->zip_fd, 1); + // ----- Check the global size + if (($v_pos + $v_data['comment_size'] + 18) != $v_size) { - // ----- Add the byte - //$v_bytes = ($v_bytes << 8) | Ord($v_byte); - // Note we mask the old value down such that once shifted we can never end up with more than a 32bit number - // Otherwise on systems where we have 64bit integers the check below for the magic number will fail. - $v_bytes = ( ($v_bytes & 0xFFFFFF) << 8) | Ord($v_byte); + // ----- Removed in release 2.2 see readme file + // The check of the file size is a little too strict. + // Some bugs where found when a zip is encrypted/decrypted with 'crypt'. + // While decrypted, zip has training 0 bytes + if (0) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'The central dir is not at the end of the archive.' . ' Some trailing bytes exists after the archive.'); - // ----- Compare the bytes - if ($v_bytes == 0x504b0506) - { - $v_pos++; - break; + // ----- Return + return PclZip::errorCode(); + } } - $v_pos++; - } + // ----- Get comment + if ($v_data['comment_size'] != 0) { + $p_central_dir['comment'] = fread($this->zip_fd, $v_data['comment_size']); + } else { + $p_central_dir['comment'] = ''; + } - // ----- Look if not found end of central dir - if ($v_pos == $v_size) - { + $p_central_dir['entries'] = $v_data['entries']; + $p_central_dir['disk_entries'] = $v_data['disk_entries']; + $p_central_dir['offset'] = $v_data['offset']; + $p_central_dir['size'] = $v_data['size']; + $p_central_dir['disk'] = $v_data['disk']; + $p_central_dir['disk_start'] = $v_data['disk_start']; - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Unable to find End of Central Dir Record signature"); + // TBC + //for (reset($p_central_dir); $key = key($p_central_dir); next($p_central_dir)) { + //} // ----- Return - return PclZip::errorCode(); - } + return $v_result; } + // -------------------------------------------------------------------------------- - // ----- Read the first 18 bytes of the header - $v_binary_data = fread($this->zip_fd, 18); - - // ----- Look for invalid block size - if (strlen($v_binary_data) != 18) + // -------------------------------------------------------------------------------- + // Function : privDeleteByRule() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privDeleteByRule(&$p_result_list, &$p_options) { + $v_result = 1; + $v_list_detail = array(); - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid End of Central Dir Record size : ".strlen($v_binary_data)); + // ----- Open the zip file + if (($v_result = $this->privOpenFd('rb')) != 1) { + // ----- Return + return $v_result; + } - // ----- Return - return PclZip::errorCode(); - } + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + $this->privCloseFd(); - // ----- Extract the values - $v_data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', $v_binary_data); - - // ----- Check the global size - if (($v_pos + $v_data['comment_size'] + 18) != $v_size) { - - // ----- Removed in release 2.2 see readme file - // The check of the file size is a little too strict. - // Some bugs where found when a zip is encrypted/decrypted with 'crypt'. - // While decrypted, zip has training 0 bytes - if (0) { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, - 'The central dir is not at the end of the archive.' - .' Some trailing bytes exists after the archive.'); - - // ----- Return - return PclZip::errorCode(); - } - } + return $v_result; + } - // ----- Get comment - if ($v_data['comment_size'] != 0) { - $p_central_dir['comment'] = fread($this->zip_fd, $v_data['comment_size']); - } - else - $p_central_dir['comment'] = ''; + // ----- Go to beginning of File + @rewind($this->zip_fd); - $p_central_dir['entries'] = $v_data['entries']; - $p_central_dir['disk_entries'] = $v_data['disk_entries']; - $p_central_dir['offset'] = $v_data['offset']; - $p_central_dir['size'] = $v_data['size']; - $p_central_dir['disk'] = $v_data['disk']; - $p_central_dir['disk_start'] = $v_data['disk_start']; + // ----- Scan all the files + // ----- Start at beginning of Central Dir + $v_pos_entry = $v_central_dir['offset']; + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_pos_entry)) { + // ----- Close the zip file + $this->privCloseFd(); - // TBC - //for(reset($p_central_dir); $key = key($p_central_dir); next($p_central_dir)) { - //} + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privDeleteByRule() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privDeleteByRule(&$p_result_list, &$p_options) - { - $v_result=1; - $v_list_detail = array(); - - // ----- Open the zip file - if (($v_result=$this->privOpenFd('rb')) != 1) - { - // ----- Return - return $v_result; - } + // ----- Return + return PclZip::errorCode(); + } - // ----- Read the central directory informations - $v_central_dir = array(); - if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) - { - $this->privCloseFd(); - return $v_result; - } + // ----- Read each entry + $v_header_list = array(); + $j_start = 0; + for ($i = 0, $v_nb_extracted = 0; $i < $v_central_dir['entries']; $i++) { - // ----- Go to beginning of File - @rewind($this->zip_fd); + // ----- Read the file header + $v_header_list[$v_nb_extracted] = array(); + if (($v_result = $this->privReadCentralFileHeader($v_header_list[$v_nb_extracted])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); - // ----- Scan all the files - // ----- Start at beginning of Central Dir - $v_pos_entry = $v_central_dir['offset']; - @rewind($this->zip_fd); - if (@fseek($this->zip_fd, $v_pos_entry)) - { - // ----- Close the zip file - $this->privCloseFd(); + return $v_result; + } - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + // ----- Store the index + $v_header_list[$v_nb_extracted]['index'] = $i; - // ----- Return - return PclZip::errorCode(); - } + // ----- Look for the specific extract rules + $v_found = false; - // ----- Read each entry - $v_header_list = array(); - $j_start = 0; - for ($i=0, $v_nb_extracted=0; $i<$v_central_dir['entries']; $i++) - { + // ----- Look for extract by name rule + if ((isset($p_options[PCLZIP_OPT_BY_NAME])) && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) { - // ----- Read the file header - $v_header_list[$v_nb_extracted] = array(); - if (($v_result = $this->privReadCentralFileHeader($v_header_list[$v_nb_extracted])) != 1) - { - // ----- Close the zip file - $this->privCloseFd(); + // ----- Look if the filename is in the list + for ($j = 0; ($j < sizeof($p_options[PCLZIP_OPT_BY_NAME])) && (!$v_found); $j++) { - return $v_result; - } - - - // ----- Store the index - $v_header_list[$v_nb_extracted]['index'] = $i; - - // ----- Look for the specific extract rules - $v_found = false; - - // ----- Look for extract by name rule - if ( (isset($p_options[PCLZIP_OPT_BY_NAME])) - && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) { - - // ----- Look if the filename is in the list - for ($j=0; ($j strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) - && (substr($v_header_list[$v_nb_extracted]['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) { - $v_found = true; - } - elseif ( (($v_header_list[$v_nb_extracted]['external']&0x00000010)==0x00000010) /* Indicates a folder */ - && ($v_header_list[$v_nb_extracted]['stored_filename'].'/' == $p_options[PCLZIP_OPT_BY_NAME][$j])) { - $v_found = true; - } - } - // ----- Look for a filename - elseif ($v_header_list[$v_nb_extracted]['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) { - $v_found = true; - } - } - } - - // ----- Look for extract by ereg rule - // ereg() is deprecated with PHP 5.3 - /* - else if ( (isset($p_options[PCLZIP_OPT_BY_EREG])) - && ($p_options[PCLZIP_OPT_BY_EREG] != "")) { - - if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header_list[$v_nb_extracted]['stored_filename'])) { - $v_found = true; - } - } - */ - - // ----- Look for extract by preg rule - else if ( (isset($p_options[PCLZIP_OPT_BY_PREG])) - && ($p_options[PCLZIP_OPT_BY_PREG] != "")) { - - if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header_list[$v_nb_extracted]['stored_filename'])) { - $v_found = true; - } - } - - // ----- Look for extract by index rule - else if ( (isset($p_options[PCLZIP_OPT_BY_INDEX])) - && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) { - - // ----- Look if the index is in the list - for ($j=$j_start; ($j=$p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i<=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) { - $v_found = true; - } - if ($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) { - $j_start = $j+1; - } - - if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start']>$i) { - break; - } - } - } - else { - $v_found = true; - } - - // ----- Look for deletion - if ($v_found) - { - unset($v_header_list[$v_nb_extracted]); - } - else - { - $v_nb_extracted++; - } - } + // ----- Look for a directory + if (substr($p_options[PCLZIP_OPT_BY_NAME][$j], -1) == "/") { - // ----- Look if something need to be deleted - if ($v_nb_extracted > 0) { + // ----- Look if the directory is in the filename path + if ((strlen($v_header_list[$v_nb_extracted]['stored_filename']) > strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) && (substr($v_header_list[$v_nb_extracted]['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) { + $v_found = true; + } elseif ((($v_header_list[$v_nb_extracted]['external'] & 0x00000010) == 0x00000010) /* Indicates a folder */ && ($v_header_list[$v_nb_extracted]['stored_filename'] . '/' == $p_options[PCLZIP_OPT_BY_NAME][$j])) { + $v_found = true; + } - // ----- Creates a temporay file - $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp'; + // ----- Look for a filename + } elseif ($v_header_list[$v_nb_extracted]['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) { + $v_found = true; + } + } - // ----- Creates a temporary zip archive - $v_temp_zip = new PclZip($v_zip_temp_name); + // ----- Look for extract by preg rule + } elseif ((isset($p_options[PCLZIP_OPT_BY_PREG])) && ($p_options[PCLZIP_OPT_BY_PREG] != "")) { - // ----- Open the temporary zip file in write mode - if (($v_result = $v_temp_zip->privOpenFd('wb')) != 1) { - $this->privCloseFd(); + if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header_list[$v_nb_extracted]['stored_filename'])) { + $v_found = true; + } - // ----- Return - return $v_result; - } + // ----- Look for extract by index rule + } elseif ((isset($p_options[PCLZIP_OPT_BY_INDEX])) && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) { - // ----- Look which file need to be kept - for ($i=0; $izip_fd); - if (@fseek($this->zip_fd, $v_header_list[$i]['offset'])) { - // ----- Close the zip file - $this->privCloseFd(); - $v_temp_zip->privCloseFd(); - @unlink($v_zip_temp_name); + if (($i >= $p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i <= $p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) { + $v_found = true; + } + if ($i >= $p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) { + $j_start = $j + 1; + } - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start'] > $i) { + break; + } + } + } else { + $v_found = true; + } - // ----- Return - return PclZip::errorCode(); + // ----- Look for deletion + if ($v_found) { + unset($v_header_list[$v_nb_extracted]); + } else { + $v_nb_extracted++; } + } - // ----- Read the file header - $v_local_header = array(); - if (($v_result = $this->privReadFileHeader($v_local_header)) != 1) { - // ----- Close the zip file + // ----- Look if something need to be deleted + if ($v_nb_extracted > 0) { + + // ----- Creates a temporay file + $v_zip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.tmp'; + + // ----- Creates a temporary zip archive + $v_temp_zip = new PclZip($v_zip_temp_name); + + // ----- Open the temporary zip file in write mode + if (($v_result = $v_temp_zip->privOpenFd('wb')) != 1) { $this->privCloseFd(); - $v_temp_zip->privCloseFd(); - @unlink($v_zip_temp_name); // ----- Return return $v_result; } - // ----- Check that local file header is same as central file header - if ($this->privCheckFileHeaders($v_local_header, - $v_header_list[$i]) != 1) { - // TBC + // ----- Look which file need to be kept + for ($i = 0; $i < sizeof($v_header_list); $i++) { + + // ----- Calculate the position of the header + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_header_list[$i]['offset'])) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read the file header + $v_local_header = array(); + if (($v_result = $this->privReadFileHeader($v_local_header)) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + + // ----- Check that local file header is same as central file header + if ($this->privCheckFileHeaders($v_local_header, $v_header_list[$i]) != 1) { + // TBC + } + unset($v_local_header); + + // ----- Write the file header + if (($v_result = $v_temp_zip->privWriteFileHeader($v_header_list[$i])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + + // ----- Read/write the data block + if (($v_result = PclZipUtilCopyBlock($this->zip_fd, $v_temp_zip->zip_fd, $v_header_list[$i]['compressed_size'])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } } - unset($v_local_header); - // ----- Write the file header - if (($v_result = $v_temp_zip->privWriteFileHeader($v_header_list[$i])) != 1) { - // ----- Close the zip file - $this->privCloseFd(); + // ----- Store the offset of the central dir + $v_offset = @ftell($v_temp_zip->zip_fd); + + // ----- Re-Create the Central Dir files header + for ($i = 0; $i < sizeof($v_header_list); $i++) { + // ----- Create the file header + if (($v_result = $v_temp_zip->privWriteCentralFileHeader($v_header_list[$i])) != 1) { + $v_temp_zip->privCloseFd(); + $this->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + + // ----- Transform the header to a 'usable' info + $v_temp_zip->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); + } + + // ----- Zip file comment + $v_comment = ''; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } + + // ----- Calculate the size of the central header + $v_size = @ftell($v_temp_zip->zip_fd) - $v_offset; + + // ----- Create the central dir footer + if (($v_result = $v_temp_zip->privWriteCentralHeader(sizeof($v_header_list), $v_size, $v_offset, $v_comment)) != 1) { + // ----- Reset the file list + unset($v_header_list); $v_temp_zip->privCloseFd(); + $this->privCloseFd(); @unlink($v_zip_temp_name); // ----- Return return $v_result; } - // ----- Read/write the data block - if (($v_result = PclZipUtilCopyBlock($this->zip_fd, $v_temp_zip->zip_fd, $v_header_list[$i]['compressed_size'])) != 1) { - // ----- Close the zip file - $this->privCloseFd(); - $v_temp_zip->privCloseFd(); - @unlink($v_zip_temp_name); + // ----- Close + $v_temp_zip->privCloseFd(); + $this->privCloseFd(); - // ----- Return + // ----- Delete the zip file + // TBC : I should test the result ... + @unlink($this->zipname); + + // ----- Rename the temporary file + // TBC : I should test the result ... + //@rename($v_zip_temp_name, $this->zipname); + PclZipUtilRename($v_zip_temp_name, $this->zipname); + + // ----- Destroy the temporary archive + unset($v_temp_zip); + + // ----- Remove every files : reset the file + } elseif ($v_central_dir['entries'] != 0) { + $this->privCloseFd(); + + if (($v_result = $this->privOpenFd('wb')) != 1) { return $v_result; } - } - // ----- Store the offset of the central dir - $v_offset = @ftell($v_temp_zip->zip_fd); + if (($v_result = $this->privWriteCentralHeader(0, 0, 0, '')) != 1) { + return $v_result; + } - // ----- Re-Create the Central Dir files header - for ($i=0; $iprivWriteCentralFileHeader($v_header_list[$i])) != 1) { - $v_temp_zip->privCloseFd(); - $this->privCloseFd(); - @unlink($v_zip_temp_name); + $this->privCloseFd(); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privDirCheck() + // Description : + // Check if a directory exists, if not it creates it and all the parents directory + // which may be useful. + // Parameters : + // $p_dir : Directory path to check. + // Return Values : + // 1 : OK + // -1 : Unable to create directory + // -------------------------------------------------------------------------------- + public function privDirCheck($p_dir, $p_is_dir = false) + { + $v_result = 1; - // ----- Return - return $v_result; - } + // ----- Remove the final '/' + if (($p_is_dir) && (substr($p_dir, -1) == '/')) { + $p_dir = substr($p_dir, 0, strlen($p_dir) - 1); + } - // ----- Transform the header to a 'usable' info - $v_temp_zip->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); + // ----- Check the directory availability + if ((is_dir($p_dir)) || ($p_dir == "")) { + return 1; } + // ----- Extract parent directory + $p_parent_dir = dirname($p_dir); - // ----- Zip file comment - $v_comment = ''; - if (isset($p_options[PCLZIP_OPT_COMMENT])) { - $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + // ----- Just a check + if ($p_parent_dir != $p_dir) { + // ----- Look for parent directory + if ($p_parent_dir != "") { + if (($v_result = $this->privDirCheck($p_parent_dir)) != 1) { + return $v_result; + } + } } - // ----- Calculate the size of the central header - $v_size = @ftell($v_temp_zip->zip_fd)-$v_offset; - - // ----- Create the central dir footer - if (($v_result = $v_temp_zip->privWriteCentralHeader(sizeof($v_header_list), $v_size, $v_offset, $v_comment)) != 1) { - // ----- Reset the file list - unset($v_header_list); - $v_temp_zip->privCloseFd(); - $this->privCloseFd(); - @unlink($v_zip_temp_name); + // ----- Create the directory + if (!@mkdir($p_dir, 0777)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_DIR_CREATE_FAIL, "Unable to create directory '$p_dir'"); // ----- Return - return $v_result; + return PclZip::errorCode(); } - // ----- Close - $v_temp_zip->privCloseFd(); - $this->privCloseFd(); - - // ----- Delete the zip file - // TBC : I should test the result ... - @unlink($this->zipname); + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- - // ----- Rename the temporary file - // TBC : I should test the result ... - //@rename($v_zip_temp_name, $this->zipname); - PclZipUtilRename($v_zip_temp_name, $this->zipname); + // -------------------------------------------------------------------------------- + // Function : privMerge() + // Description : + // If $p_archive_to_add does not exist, the function exit with a success result. + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privMerge(&$p_archive_to_add) + { + $v_result = 1; - // ----- Destroy the temporary archive - unset($v_temp_zip); - } + // ----- Look if the archive_to_add exists + if (!is_file($p_archive_to_add->zipname)) { - // ----- Remove every files : reset the file - else if ($v_central_dir['entries'] != 0) { - $this->privCloseFd(); + // ----- Nothing to merge, so merge is a success + $v_result = 1; - if (($v_result = $this->privOpenFd('wb')) != 1) { - return $v_result; + // ----- Return + return $v_result; } - if (($v_result = $this->privWriteCentralHeader(0, 0, 0, '')) != 1) { - return $v_result; - } + // ----- Look if the archive exists + if (!is_file($this->zipname)) { - $this->privCloseFd(); - } + // ----- Do a duplicate + $v_result = $this->privDuplicate($p_archive_to_add->zipname); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privDirCheck() - // Description : - // Check if a directory exists, if not it creates it and all the parents directory - // which may be useful. - // Parameters : - // $p_dir : Directory path to check. - // Return Values : - // 1 : OK - // -1 : Unable to create directory - // -------------------------------------------------------------------------------- - function privDirCheck($p_dir, $p_is_dir=false) - { - $v_result = 1; + // ----- Return + return $v_result; + } + // ----- Open the zip file + if (($v_result = $this->privOpenFd('rb')) != 1) { + // ----- Return + return $v_result; + } - // ----- Remove the final '/' - if (($p_is_dir) && (substr($p_dir, -1)=='/')) - { - $p_dir = substr($p_dir, 0, strlen($p_dir)-1); - } + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + $this->privCloseFd(); - // ----- Check the directory availability - if ((is_dir($p_dir)) || ($p_dir == "")) - { - return 1; - } + return $v_result; + } - // ----- Extract parent directory - $p_parent_dir = dirname($p_dir); + // ----- Go to beginning of File + @rewind($this->zip_fd); - // ----- Just a check - if ($p_parent_dir != $p_dir) - { - // ----- Look for parent directory - if ($p_parent_dir != "") - { - if (($v_result = $this->privDirCheck($p_parent_dir)) != 1) - { - return $v_result; - } - } - } + // ----- Open the archive_to_add file + if (($v_result = $p_archive_to_add->privOpenFd('rb')) != 1) { + $this->privCloseFd(); - // ----- Create the directory - if (!@mkdir($p_dir, 0777)) - { - // ----- Error log - PclZip::privErrorLog(PCLZIP_ERR_DIR_CREATE_FAIL, "Unable to create directory '$p_dir'"); + // ----- Return + return $v_result; + } - // ----- Return - return PclZip::errorCode(); - } + // ----- Read the central directory informations + $v_central_dir_to_add = array(); + if (($v_result = $p_archive_to_add->privReadEndCentralDir($v_central_dir_to_add)) != 1) { + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privMerge() - // Description : - // If $p_archive_to_add does not exist, the function exit with a success result. - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privMerge(&$p_archive_to_add) - { - $v_result=1; - - // ----- Look if the archive_to_add exists - if (!is_file($p_archive_to_add->zipname)) - { + return $v_result; + } - // ----- Nothing to merge, so merge is a success - $v_result = 1; + // ----- Go to beginning of File + @rewind($p_archive_to_add->zip_fd); - // ----- Return - return $v_result; - } + // ----- Creates a temporay file + $v_zip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.tmp'; - // ----- Look if the archive exists - if (!is_file($this->zipname)) - { + // ----- Open the temporary file in write mode + if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0) { + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); - // ----- Do a duplicate - $v_result = $this->privDuplicate($p_archive_to_add->zipname); + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_zip_temp_name . '\' in binary write mode'); - // ----- Return - return $v_result; - } + // ----- Return + return PclZip::errorCode(); + } - // ----- Open the zip file - if (($v_result=$this->privOpenFd('rb')) != 1) - { - // ----- Return - return $v_result; - } + // ----- Copy the files from the archive to the temporary file + // TBC : Here I should better append the file and go back to erase the central dir + $v_size = $v_central_dir['offset']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($this->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } - // ----- Read the central directory informations - $v_central_dir = array(); - if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) - { - $this->privCloseFd(); - return $v_result; - } + // ----- Copy the files from the archive_to_add into the temporary file + $v_size = $v_central_dir_to_add['offset']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($p_archive_to_add->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } - // ----- Go to beginning of File - @rewind($this->zip_fd); + // ----- Store the offset of the central dir + $v_offset = @ftell($v_zip_temp_fd); - // ----- Open the archive_to_add file - if (($v_result=$p_archive_to_add->privOpenFd('rb')) != 1) - { - $this->privCloseFd(); + // ----- Copy the block of file headers from the old archive + $v_size = $v_central_dir['size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($this->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } - // ----- Return - return $v_result; - } + // ----- Copy the block of file headers from the archive_to_add + $v_size = $v_central_dir_to_add['size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($p_archive_to_add->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } - // ----- Read the central directory informations - $v_central_dir_to_add = array(); - if (($v_result = $p_archive_to_add->privReadEndCentralDir($v_central_dir_to_add)) != 1) - { - $this->privCloseFd(); - $p_archive_to_add->privCloseFd(); + // ----- Merge the file comments + $v_comment = $v_central_dir['comment'] . ' ' . $v_central_dir_to_add['comment']; - return $v_result; - } + // ----- Calculate the size of the (new) central header + $v_size = @ftell($v_zip_temp_fd) - $v_offset; - // ----- Go to beginning of File - @rewind($p_archive_to_add->zip_fd); + // ----- Swap the file descriptor + // Here is a trick : I swap the temporary fd with the zip fd, in order to use + // the following methods on the temporary fil and not the real archive fd + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; - // ----- Creates a temporay file - $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp'; + // ----- Create the central dir footer + if (($v_result = $this->privWriteCentralHeader($v_central_dir['entries'] + $v_central_dir_to_add['entries'], $v_size, $v_offset, $v_comment)) != 1) { + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); + @fclose($v_zip_temp_fd); + $this->zip_fd = null; - // ----- Open the temporary file in write mode - if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0) - { - $this->privCloseFd(); - $p_archive_to_add->privCloseFd(); + // ----- Reset the file list + unset($v_header_list); + + // ----- Return + return $v_result; + } - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_zip_temp_name.'\' in binary write mode'); + // ----- Swap back the file descriptor + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; - // ----- Return - return PclZip::errorCode(); - } + // ----- Close + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); - // ----- Copy the files from the archive to the temporary file - // TBC : Here I should better append the file and go back to erase the central dir - $v_size = $v_central_dir['offset']; - while ($v_size != 0) - { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = fread($this->zip_fd, $v_read_size); - @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); - $v_size -= $v_read_size; - } + // ----- Close the temporary file + @fclose($v_zip_temp_fd); - // ----- Copy the files from the archive_to_add into the temporary file - $v_size = $v_central_dir_to_add['offset']; - while ($v_size != 0) - { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = fread($p_archive_to_add->zip_fd, $v_read_size); - @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); - $v_size -= $v_read_size; - } + // ----- Delete the zip file + // TBC : I should test the result ... + @unlink($this->zipname); - // ----- Store the offset of the central dir - $v_offset = @ftell($v_zip_temp_fd); + // ----- Rename the temporary file + // TBC : I should test the result ... + //@rename($v_zip_temp_name, $this->zipname); + PclZipUtilRename($v_zip_temp_name, $this->zipname); - // ----- Copy the block of file headers from the old archive - $v_size = $v_central_dir['size']; - while ($v_size != 0) - { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @fread($this->zip_fd, $v_read_size); - @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); - $v_size -= $v_read_size; + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- - // ----- Copy the block of file headers from the archive_to_add - $v_size = $v_central_dir_to_add['size']; - while ($v_size != 0) + // -------------------------------------------------------------------------------- + // Function : privDuplicate() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privDuplicate($p_archive_filename) { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @fread($p_archive_to_add->zip_fd, $v_read_size); - @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); - $v_size -= $v_read_size; - } + $v_result = 1; - // ----- Merge the file comments - $v_comment = $v_central_dir['comment'].' '.$v_central_dir_to_add['comment']; + // ----- Look if the $p_archive_filename exists + if (!is_file($p_archive_filename)) { - // ----- Calculate the size of the (new) central header - $v_size = @ftell($v_zip_temp_fd)-$v_offset; + // ----- Nothing to duplicate, so duplicate is a success. + $v_result = 1; - // ----- Swap the file descriptor - // Here is a trick : I swap the temporary fd with the zip fd, in order to use - // the following methods on the temporary fil and not the real archive fd - $v_swap = $this->zip_fd; - $this->zip_fd = $v_zip_temp_fd; - $v_zip_temp_fd = $v_swap; + // ----- Return + return $v_result; + } - // ----- Create the central dir footer - if (($v_result = $this->privWriteCentralHeader($v_central_dir['entries']+$v_central_dir_to_add['entries'], $v_size, $v_offset, $v_comment)) != 1) - { - $this->privCloseFd(); - $p_archive_to_add->privCloseFd(); - @fclose($v_zip_temp_fd); - $this->zip_fd = null; + // ----- Open the zip file + if (($v_result = $this->privOpenFd('wb')) != 1) { + // ----- Return + return $v_result; + } - // ----- Reset the file list - unset($v_header_list); + // ----- Open the temporary file in write mode + if (($v_zip_temp_fd = @fopen($p_archive_filename, 'rb')) == 0) { + $this->privCloseFd(); - // ----- Return - return $v_result; - } + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive file \'' . $p_archive_filename . '\' in binary write mode'); - // ----- Swap back the file descriptor - $v_swap = $this->zip_fd; - $this->zip_fd = $v_zip_temp_fd; - $v_zip_temp_fd = $v_swap; + // ----- Return + return PclZip::errorCode(); + } - // ----- Close - $this->privCloseFd(); - $p_archive_to_add->privCloseFd(); + // ----- Copy the files from the archive to the temporary file + // TBC : Here I should better append the file and go back to erase the central dir + $v_size = filesize($p_archive_filename); + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($v_zip_temp_fd, $v_read_size); + @fwrite($this->zip_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } - // ----- Close the temporary file - @fclose($v_zip_temp_fd); + // ----- Close + $this->privCloseFd(); - // ----- Delete the zip file - // TBC : I should test the result ... - @unlink($this->zipname); + // ----- Close the temporary file + @fclose($v_zip_temp_fd); - // ----- Rename the temporary file - // TBC : I should test the result ... - //@rename($v_zip_temp_name, $this->zipname); - PclZipUtilRename($v_zip_temp_name, $this->zipname); + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privDuplicate() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privDuplicate($p_archive_filename) - { - $v_result=1; - - // ----- Look if the $p_archive_filename exists - if (!is_file($p_archive_filename)) + // -------------------------------------------------------------------------------- + // Function : privErrorLog() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function privErrorLog($p_error_code = 0, $p_error_string = '') { - - // ----- Nothing to duplicate, so duplicate is a success. - $v_result = 1; - - // ----- Return - return $v_result; + if (PCLZIP_ERROR_EXTERNAL == 1) { + PclError($p_error_code, $p_error_string); + } else { + $this->error_code = $p_error_code; + $this->error_string = $p_error_string; + } } + // -------------------------------------------------------------------------------- - // ----- Open the zip file - if (($v_result=$this->privOpenFd('wb')) != 1) + // -------------------------------------------------------------------------------- + // Function : privErrorReset() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function privErrorReset() { - // ----- Return - return $v_result; + if (PCLZIP_ERROR_EXTERNAL == 1) { + PclErrorReset(); + } else { + $this->error_code = 0; + $this->error_string = ''; + } } + // -------------------------------------------------------------------------------- - // ----- Open the temporary file in write mode - if (($v_zip_temp_fd = @fopen($p_archive_filename, 'rb')) == 0) + // -------------------------------------------------------------------------------- + // Function : privDisableMagicQuotes() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privDisableMagicQuotes() { - $this->privCloseFd(); - - PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive file \''.$p_archive_filename.'\' in binary write mode'); + $v_result = 1; - // ----- Return - return PclZip::errorCode(); - } + // ----- Look if function exists + if ((!function_exists("get_magic_quotes_runtime")) || (!function_exists("set_magic_quotes_runtime"))) { + return $v_result; + } - // ----- Copy the files from the archive to the temporary file - // TBC : Here I should better append the file and go back to erase the central dir - $v_size = filesize($p_archive_filename); - while ($v_size != 0) - { - $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = fread($v_zip_temp_fd, $v_read_size); - @fwrite($this->zip_fd, $v_buffer, $v_read_size); - $v_size -= $v_read_size; - } + // ----- Look if already done + if ($this->magic_quotes_status != -1) { + return $v_result; + } - // ----- Close - $this->privCloseFd(); + // ----- Get and memorize the magic_quote value + $this->magic_quotes_status = @get_magic_quotes_runtime(); - // ----- Close the temporary file - @fclose($v_zip_temp_fd); + // ----- Disable magic_quotes + if ($this->magic_quotes_status == 1) { + @set_magic_quotes_runtime(0); + } - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privErrorLog() - // Description : - // Parameters : - // -------------------------------------------------------------------------------- - function privErrorLog($p_error_code=0, $p_error_string='') - { - if (PCLZIP_ERROR_EXTERNAL == 1) { - PclError($p_error_code, $p_error_string); - } - else { - $this->error_code = $p_error_code; - $this->error_string = $p_error_string; - } - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privErrorReset() - // Description : - // Parameters : - // -------------------------------------------------------------------------------- - function privErrorReset() - { - if (PCLZIP_ERROR_EXTERNAL == 1) { - PclErrorReset(); - } - else { - $this->error_code = 0; - $this->error_string = ''; - } - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privDisableMagicQuotes() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privDisableMagicQuotes() - { - $v_result=1; - - // ----- Look if function exists - if ( (!function_exists("get_magic_quotes_runtime")) - || (!function_exists("set_magic_quotes_runtime"))) { - return $v_result; + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- - // ----- Look if already done - if ($this->magic_quotes_status != -1) { - return $v_result; - } + // -------------------------------------------------------------------------------- + // Function : privSwapBackMagicQuotes() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privSwapBackMagicQuotes() + { + $v_result = 1; - // ----- Get and memorize the magic_quote value - $this->magic_quotes_status = @get_magic_quotes_runtime(); + // ----- Look if function exists + if ((!function_exists("get_magic_quotes_runtime")) || (!function_exists("set_magic_quotes_runtime"))) { + return $v_result; + } - // ----- Disable magic_quotes - if ($this->magic_quotes_status == 1) { - @set_magic_quotes_runtime(0); - } + // ----- Look if something to do + if ($this->magic_quotes_status != -1) { + return $v_result; + } - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : privSwapBackMagicQuotes() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function privSwapBackMagicQuotes() - { - $v_result=1; - - // ----- Look if function exists - if ( (!function_exists("get_magic_quotes_runtime")) - || (!function_exists("set_magic_quotes_runtime"))) { - return $v_result; - } + // ----- Swap back magic_quotes + if ($this->magic_quotes_status == 1) { + @set_magic_quotes_runtime($this->magic_quotes_status); + } - // ----- Look if something to do - if ($this->magic_quotes_status != -1) { - return $v_result; + // ----- Return + return $v_result; } + // -------------------------------------------------------------------------------- +} - // ----- Swap back magic_quotes - if ($this->magic_quotes_status == 1) { - @set_magic_quotes_runtime($this->magic_quotes_status); - } +// End of class +// -------------------------------------------------------------------------------- - // ----- Return - return $v_result; - } - // -------------------------------------------------------------------------------- - - } - // End of class - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : PclZipUtilPathReduction() - // Description : - // Parameters : - // Return Values : - // -------------------------------------------------------------------------------- - function PclZipUtilPathReduction($p_dir) - { +// -------------------------------------------------------------------------------- +// Function : PclZipUtilPathReduction() +// Description : +// Parameters : +// Return Values : +// -------------------------------------------------------------------------------- +function PclZipUtilPathReduction($p_dir) +{ $v_result = ""; // ----- Look for not empty path if ($p_dir != "") { - // ----- Explode path by directory names - $v_list = explode("/", $p_dir); - - // ----- Study directories from last to first - $v_skip = 0; - for ($i=sizeof($v_list)-1; $i>=0; $i--) { - // ----- Look for current path - if ($v_list[$i] == ".") { - // ----- Ignore this directory - // Should be the first $i=0, but no check is done - } - else if ($v_list[$i] == "..") { - $v_skip++; - } - else if ($v_list[$i] == "") { - // ----- First '/' i.e. root slash - if ($i == 0) { - $v_result = "/".$v_result; - if ($v_skip > 0) { - // ----- It is an invalid path, so the path is not modified - // TBC - $v_result = $p_dir; - $v_skip = 0; + // ----- Explode path by directory names + $v_list = explode("/", $p_dir); + + // ----- Study directories from last to first + $v_skip = 0; + for ($i = sizeof($v_list) - 1; $i >= 0; $i--) { + // ----- Look for current path + if ($v_list[$i] == ".") { + // ----- Ignore this directory + // Should be the first $i=0, but no check is done + } elseif ($v_list[$i] == "..") { + $v_skip++; + } elseif ($v_list[$i] == "") { + // ----- First '/' i.e. root slash + if ($i == 0) { + $v_result = "/" . $v_result; + if ($v_skip > 0) { + // ----- It is an invalid path, so the path is not modified + // TBC + $v_result = $p_dir; + $v_skip = 0; + } + + // ----- Last '/' i.e. indicates a directory + } elseif ($i == (sizeof($v_list) - 1)) { + $v_result = $v_list[$i]; + + // ----- Double '/' inside the path + } else { + // ----- Ignore only the double '//' in path, + // but not the first and last '/' + } + } else { + // ----- Look for item to skip + if ($v_skip > 0) { + $v_skip--; + } else { + $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? "/" . $v_result : ""); + } + } + } + + // ----- Look for skip + if ($v_skip > 0) { + while ($v_skip > 0) { + $v_result = '../' . $v_result; + $v_skip--; } - } - // ----- Last '/' i.e. indicates a directory - else if ($i == (sizeof($v_list)-1)) { - $v_result = $v_list[$i]; - } - // ----- Double '/' inside the path - else { - // ----- Ignore only the double '//' in path, - // but not the first and last '/' - } - } - else { - // ----- Look for item to skip - if ($v_skip > 0) { - $v_skip--; - } - else { - $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?"/".$v_result:""); - } - } - } - - // ----- Look for skip - if ($v_skip > 0) { - while ($v_skip > 0) { - $v_result = '../'.$v_result; - $v_skip--; - } - } + } } // ----- Return return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : PclZipUtilPathInclusion() - // Description : - // This function indicates if the path $p_path is under the $p_dir tree. Or, - // said in an other way, if the file or sub-dir $p_path is inside the dir - // $p_dir. - // The function indicates also if the path is exactly the same as the dir. - // This function supports path with duplicated '/' like '//', but does not - // support '.' or '..' statements. - // Parameters : - // Return Values : - // 0 if $p_path is not inside directory $p_dir - // 1 if $p_path is inside directory $p_dir - // 2 if $p_path is exactly the same as $p_dir - // -------------------------------------------------------------------------------- - function PclZipUtilPathInclusion($p_dir, $p_path) - { +} +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Function : PclZipUtilPathInclusion() +// Description : +// This function indicates if the path $p_path is under the $p_dir tree. Or, +// said in an other way, if the file or sub-dir $p_path is inside the dir +// $p_dir. +// The function indicates also if the path is exactly the same as the dir. +// This function supports path with duplicated '/' like '//', but does not +// support '.' or '..' statements. +// Parameters : +// Return Values : +// 0 if $p_path is not inside directory $p_dir +// 1 if $p_path is inside directory $p_dir +// 2 if $p_path is exactly the same as $p_dir +// -------------------------------------------------------------------------------- +function PclZipUtilPathInclusion($p_dir, $p_path) +{ $v_result = 1; // ----- Look for path beginning by ./ - if ( ($p_dir == '.') - || ((strlen($p_dir) >=2) && (substr($p_dir, 0, 2) == './'))) { - $p_dir = PclZipUtilTranslateWinPath(getcwd(), FALSE).'/'.substr($p_dir, 1); + if (($p_dir == '.') || ((strlen($p_dir) >= 2) && (substr($p_dir, 0, 2) == './'))) { + $p_dir = PclZipUtilTranslateWinPath(getcwd(), false) . '/' . substr($p_dir, 1); } - if ( ($p_path == '.') - || ((strlen($p_path) >=2) && (substr($p_path, 0, 2) == './'))) { - $p_path = PclZipUtilTranslateWinPath(getcwd(), FALSE).'/'.substr($p_path, 1); + if (($p_path == '.') || ((strlen($p_path) >= 2) && (substr($p_path, 0, 2) == './'))) { + $p_path = PclZipUtilTranslateWinPath(getcwd(), false) . '/' . substr($p_path, 1); } // ----- Explode dir and path by directory separator - $v_list_dir = explode("/", $p_dir); - $v_list_dir_size = sizeof($v_list_dir); - $v_list_path = explode("/", $p_path); + $v_list_dir = explode("/", $p_dir); + $v_list_dir_size = sizeof($v_list_dir); + $v_list_path = explode("/", $p_path); $v_list_path_size = sizeof($v_list_path); // ----- Study directories paths @@ -5499,193 +5211,182 @@ function PclZipUtilPathInclusion($p_dir, $p_path) $j = 0; while (($i < $v_list_dir_size) && ($j < $v_list_path_size) && ($v_result)) { - // ----- Look for empty dir (path reduction) - if ($v_list_dir[$i] == '') { - $i++; - continue; - } - if ($v_list_path[$j] == '') { - $j++; - continue; - } + // ----- Look for empty dir (path reduction) + if ($v_list_dir[$i] == '') { + $i++; + continue; + } + if ($v_list_path[$j] == '') { + $j++; + continue; + } - // ----- Compare the items - if (($v_list_dir[$i] != $v_list_path[$j]) && ($v_list_dir[$i] != '') && ( $v_list_path[$j] != '')) { - $v_result = 0; - } + // ----- Compare the items + if (($v_list_dir[$i] != $v_list_path[$j]) && ($v_list_dir[$i] != '') && ($v_list_path[$j] != '')) { + $v_result = 0; + } - // ----- Next items - $i++; - $j++; + // ----- Next items + $i++; + $j++; } // ----- Look if everything seems to be the same if ($v_result) { - // ----- Skip all the empty items - while (($j < $v_list_path_size) && ($v_list_path[$j] == '')) $j++; - while (($i < $v_list_dir_size) && ($v_list_dir[$i] == '')) $i++; - - if (($i >= $v_list_dir_size) && ($j >= $v_list_path_size)) { - // ----- There are exactly the same - $v_result = 2; - } - else if ($i < $v_list_dir_size) { - // ----- The path is shorter than the dir - $v_result = 0; - } + // ----- Skip all the empty items + while (($j < $v_list_path_size) && ($v_list_path[$j] == '')) { + $j++; + } + while (($i < $v_list_dir_size) && ($v_list_dir[$i] == '')) { + $i++; + } + + if (($i >= $v_list_dir_size) && ($j >= $v_list_path_size)) { + // ----- There are exactly the same + $v_result = 2; + } elseif ($i < $v_list_dir_size) { + // ----- The path is shorter than the dir + $v_result = 0; + } } // ----- Return return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : PclZipUtilCopyBlock() - // Description : - // Parameters : - // $p_mode : read/write compression mode - // 0 : src & dest normal - // 1 : src gzip, dest normal - // 2 : src normal, dest gzip - // 3 : src & dest gzip - // Return Values : - // -------------------------------------------------------------------------------- - function PclZipUtilCopyBlock($p_src, $p_dest, $p_size, $p_mode=0) - { +} +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Function : PclZipUtilCopyBlock() +// Description : +// Parameters : +// $p_mode : read/write compression mode +// 0 : src & dest normal +// 1 : src gzip, dest normal +// 2 : src normal, dest gzip +// 3 : src & dest gzip +// Return Values : +// -------------------------------------------------------------------------------- +function PclZipUtilCopyBlock($p_src, $p_dest, $p_size, $p_mode = 0) +{ $v_result = 1; - if ($p_mode==0) - { - while ($p_size != 0) - { - $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @fread($p_src, $v_read_size); - @fwrite($p_dest, $v_buffer, $v_read_size); - $p_size -= $v_read_size; - } - } - else if ($p_mode==1) - { - while ($p_size != 0) - { - $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @gzread($p_src, $v_read_size); - @fwrite($p_dest, $v_buffer, $v_read_size); - $p_size -= $v_read_size; - } - } - else if ($p_mode==2) - { - while ($p_size != 0) - { - $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @fread($p_src, $v_read_size); - @gzwrite($p_dest, $v_buffer, $v_read_size); - $p_size -= $v_read_size; - } - } - else if ($p_mode==3) - { - while ($p_size != 0) - { - $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); - $v_buffer = @gzread($p_src, $v_read_size); - @gzwrite($p_dest, $v_buffer, $v_read_size); - $p_size -= $v_read_size; - } + if ($p_mode == 0) { + while ($p_size != 0) { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($p_src, $v_read_size); + @fwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } elseif ($p_mode == 1) { + while ($p_size != 0) { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @gzread($p_src, $v_read_size); + @fwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } elseif ($p_mode == 2) { + while ($p_size != 0) { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($p_src, $v_read_size); + @gzwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } elseif ($p_mode == 3) { + while ($p_size != 0) { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @gzread($p_src, $v_read_size); + @gzwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } } // ----- Return return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : PclZipUtilRename() - // Description : - // This function tries to do a simple rename() function. If it fails, it - // tries to copy the $p_src file in a new $p_dest file and then unlink the - // first one. - // Parameters : - // $p_src : Old filename - // $p_dest : New filename - // Return Values : - // 1 on success, 0 on failure. - // -------------------------------------------------------------------------------- - function PclZipUtilRename($p_src, $p_dest) - { +} +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Function : PclZipUtilRename() +// Description : +// This function tries to do a simple rename() function. If it fails, it +// tries to copy the $p_src file in a new $p_dest file and then unlink the +// first one. +// Parameters : +// $p_src : Old filename +// $p_dest : New filename +// Return Values : +// 1 on success, 0 on failure. +// -------------------------------------------------------------------------------- +function PclZipUtilRename($p_src, $p_dest) +{ $v_result = 1; // ----- Try to rename the files if (!@rename($p_src, $p_dest)) { - // ----- Try to copy & unlink the src - if (!@copy($p_src, $p_dest)) { - $v_result = 0; - } - else if (!@unlink($p_src)) { - $v_result = 0; - } + // ----- Try to copy & unlink the src + if (!@copy($p_src, $p_dest)) { + $v_result = 0; + } elseif (!@unlink($p_src)) { + $v_result = 0; + } } // ----- Return return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : PclZipUtilOptionText() - // Description : - // Translate option value in text. Mainly for debug purpose. - // Parameters : - // $p_option : the option value. - // Return Values : - // The option text value. - // -------------------------------------------------------------------------------- - function PclZipUtilOptionText($p_option) - { +} +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Function : PclZipUtilOptionText() +// Description : +// Translate option value in text. Mainly for debug purpose. +// Parameters : +// $p_option : the option value. +// Return Values : +// The option text value. +// -------------------------------------------------------------------------------- +function PclZipUtilOptionText($p_option) +{ $v_list = get_defined_constants(); for (reset($v_list); $v_key = key($v_list); next($v_list)) { $v_prefix = substr($v_key, 0, 10); - if (( ($v_prefix == 'PCLZIP_OPT') - || ($v_prefix == 'PCLZIP_CB_') - || ($v_prefix == 'PCLZIP_ATT')) - && ($v_list[$v_key] == $p_option)) { - return $v_key; + if ((($v_prefix == 'PCLZIP_OPT') || ($v_prefix == 'PCLZIP_CB_') || ($v_prefix == 'PCLZIP_ATT')) && ($v_list[$v_key] == $p_option)) { + return $v_key; } } $v_result = 'Unknown'; return $v_result; - } - // -------------------------------------------------------------------------------- - - // -------------------------------------------------------------------------------- - // Function : PclZipUtilTranslateWinPath() - // Description : - // Translate windows path by replacing '\' by '/' and optionally removing - // drive letter. - // Parameters : - // $p_path : path to translate. - // $p_remove_disk_letter : true | false - // Return Values : - // The path translated. - // -------------------------------------------------------------------------------- - function PclZipUtilTranslateWinPath($p_path, $p_remove_disk_letter=true) - { +} +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Function : PclZipUtilTranslateWinPath() +// Description : +// Translate windows path by replacing '\' by '/' and optionally removing +// drive letter. +// Parameters : +// $p_path : path to translate. +// $p_remove_disk_letter : true | false +// Return Values : +// The path translated. +// -------------------------------------------------------------------------------- +function PclZipUtilTranslateWinPath($p_path, $p_remove_disk_letter = true) +{ if (stristr(php_uname(), 'windows')) { - // ----- Look for potential disk letter - if (($p_remove_disk_letter) && (($v_position = strpos($p_path, ':')) != false)) { - $p_path = substr($p_path, $v_position+1); - } - // ----- Change potential windows directory separator - if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { - $p_path = strtr($p_path, '\\', '/'); - } + // ----- Look for potential disk letter + if (($p_remove_disk_letter) && (($v_position = strpos($p_path, ':')) != false)) { + $p_path = substr($p_path, $v_position + 1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } } + return $p_path; - } - // -------------------------------------------------------------------------------- +} +// -------------------------------------------------------------------------------- diff --git a/src/PhpWord/Shared/String.php b/src/PhpWord/Shared/Text.php similarity index 57% rename from src/PhpWord/Shared/String.php rename to src/PhpWord/Shared/Text.php index be04fd0ec7..251764b3dd 100644 --- a/src/PhpWord/Shared/String.php +++ b/src/PhpWord/Shared/Text.php @@ -1,4 +1,5 @@ ) + * element or in the shared string element. + * + * @param string $value Value to escape * - * @param string $value Value to unescape * @return string */ - public static function controlCharacterOOXML2PHP($value = '') + public static function controlCharacterPHP2OOXML($value = '') { if (empty(self::$controlCharacters)) { self::buildControlCharacters(); } - return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $value); + return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value); } /** - * Convert from PHP control character to OpenXML escaped control character + * Return a number formatted for being integrated in xml files. + * + * @param float $number + * @param int $decimals * - * @param string $value Value to escape * @return string */ - public static function controlCharacterPHP2OOXML($value = '') + public static function numberFormat($number, $decimals) + { + return number_format($number, $decimals, '.', ''); + } + + /** + * @param int $dec + * + * @see http://stackoverflow.com/a/7153133/2235790 + * + * @author velcrow + * + * @return string + */ + public static function chr($dec) + { + if ($dec <= 0x7F) { + return chr($dec); + } + if ($dec <= 0x7FF) { + return chr(($dec >> 6) + 192) . chr(($dec & 63) + 128); + } + if ($dec <= 0xFFFF) { + return chr(($dec >> 12) + 224) . chr((($dec >> 6) & 63) + 128) . chr(($dec & 63) + 128); + } + if ($dec <= 0x1FFFFF) { + return chr(($dec >> 18) + 240) . chr((($dec >> 12) & 63) + 128) . chr((($dec >> 6) & 63) + 128) . chr(($dec & 63) + 128); + } + + return ''; + } + + /** + * Convert from OpenXML escaped control character to PHP control character. + * + * @param string $value Value to unescape + * + * @return string + */ + public static function controlCharacterOOXML2PHP($value = '') { if (empty(self::$controlCharacters)) { self::buildControlCharacters(); } - return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value); + return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $value); } /** - * Check if a string contains UTF-8 data + * Check if a string contains UTF-8 data. * * @param string $value - * @return boolean + * + * @return bool */ public static function isUTF8($value = '') { - return $value === '' || preg_match('/^./su', $value) === 1; + return is_string($value) && ($value === '' || preg_match('/^./su', $value) == 1); } /** - * Return UTF8 encoded value + * Return UTF8 encoded value. * - * @param string $value - * @return string + * @param null|string $value + * + * @return ?string */ public static function toUTF8($value = '') { - if (!is_null($value) && !self::isUTF8($value)) { - $value = utf8_encode($value); + if (null !== $value && !self::isUTF8($value)) { + // PHP8.2 : utf8_encode is deprecated, but mb_convert_encoding always usable + $value = (function_exists('mb_convert_encoding')) ? mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1') : utf8_encode($value); + if ($value === false) { + throw new Exception('Unable to convert text to UTF-8'); + } } return $value; } /** - * Returns unicode from UTF8 text + * Returns unicode from UTF8 text. * * The function is splitted to reduce cyclomatic complexity * * @param string $text UTF8 text + * * @return string Unicode text + * * @since 0.11.0 */ public static function toUnicode($text) @@ -100,21 +175,23 @@ public static function toUnicode($text) } /** - * Returns unicode array from UTF8 text + * Returns unicode array from UTF8 text. * * @param string $text UTF8 text + * * @return array + * * @since 0.11.0 - * @link http://www.randomchaos.com/documents/?source=php_and_unicode + * @see http://www.randomchaos.com/documents/?source=php_and_unicode */ - private static function utf8ToUnicode($text) + public static function utf8ToUnicode($text) { - $unicode = array(); - $values = array(); + $unicode = []; + $values = []; $lookingFor = 1; // Gets unicode for each character - for ($i = 0; $i < strlen($text); $i++) { + for ($i = 0; $i < strlen($text); ++$i) { $thisValue = ord($text[$i]); if ($thisValue < 128) { $unicode[] = $thisValue; @@ -130,7 +207,7 @@ private static function utf8ToUnicode($text) $number = (($values[0] % 32) * 64) + ($values[1] % 64); } $unicode[] = $number; - $values = array(); + $values = []; $lookingFor = 1; } } @@ -140,12 +217,14 @@ private static function utf8ToUnicode($text) } /** - * Returns entites from unicode array + * Returns entites from unicode array. * * @param array $unicode + * * @return string + * * @since 0.11.0 - * @link http://www.randomchaos.com/documents/?source=php_and_unicode + * @see http://www.randomchaos.com/documents/?source=php_and_unicode */ private static function unicodeToEntities($unicode) { @@ -161,14 +240,15 @@ private static function unicodeToEntities($unicode) } /** - * Return name without underscore for < 0.10.0 variable name compatibility + * Return name without underscore for < 0.10.0 variable name compatibility. * * @param string $value + * * @return string */ public static function removeUnderscorePrefix($value) { - if (!is_null($value)) { + if (null !== $value) { if (substr($value, 0, 1) == '_') { $value = substr($value, 1); } @@ -176,20 +256,4 @@ public static function removeUnderscorePrefix($value) return $value; } - - /** - * Build control characters array. - * - * @return void - */ - private static function buildControlCharacters() - { - for ($i = 0; $i <= 19; ++$i) { - if ($i != 9 && $i != 10 && $i != 13) { - $find = '_x' . sprintf('%04s', strtoupper(dechex($i))) . '_'; - $replace = chr($i); - self::$controlCharacters[$find] = $replace; - } - } - } } diff --git a/src/PhpWord/Shared/Validate.php b/src/PhpWord/Shared/Validate.php new file mode 100644 index 0000000000..faa7df0db4 --- /dev/null +++ b/src/PhpWord/Shared/Validate.php @@ -0,0 +1,77 @@ +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) { return false; - } else { - return $this->getDomFromString($content); } + + return $this->getDomFromString($content); } /** - * Get DOMDocument from content string + * Get DOMDocument from content string. * * @param string $content - * @return \DOMDocument + * + * @return DOMDocument */ public function getDomFromString($content) { - $this->dom = new \DOMDocument(); + if (\PHP_VERSION_ID < 80000) { + $originalLibXMLEntityValue = libxml_disable_entity_loader(true); + } + $this->dom = new DOMDocument(); $this->dom->loadXML($content); + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($originalLibXMLEntityValue); + } return $this->dom; } /** - * Get elements + * Get elements. * * @param string $path - * @param \DOMElement $contextNode - * @return \DOMNodeList + * + * @return DOMNodeList */ - public function getElements($path, \DOMElement $contextNode = null) + public function getElements($path, ?DOMElement $contextNode = null) { if ($this->dom === null) { - return array(); + return new DOMNodeList(); // @phpstan-ignore-line } if ($this->xpath === null) { - $this->xpath = new \DOMXpath($this->dom); + $this->xpath = new DOMXpath($this->dom); } - if (is_null($contextNode)) { - return $this->xpath->query($path); - } else { - return $this->xpath->query($path, $contextNode); + $result = @$this->xpath->query($path, $contextNode); + + return empty($result) ? new DOMNodeList() : $result; // @phpstan-ignore-line + } + + /** + * Registers the namespace with the DOMXPath object. + * + * @param string $prefix The prefix + * @param string $namespaceURI The URI of the namespace + * + * @return bool true on success or false on failure + */ + public function registerNamespace($prefix, $namespaceURI) + { + if ($this->dom === null) { + throw new InvalidArgumentException('Dom needs to be loaded before registering a namespace'); } + if ($this->xpath === null) { + $this->xpath = new DOMXpath($this->dom); + } + + return $this->xpath->registerNamespace($prefix, $namespaceURI); } /** - * Get element + * Get element. * * @param string $path - * @param \DOMElement $contextNode - * @return \DOMElement|null + * + * @return null|DOMElement */ - public function getElement($path, \DOMElement $contextNode = null) + public function getElement($path, ?DOMElement $contextNode = null) { $elements = $this->getElements($path, $contextNode); if ($elements->length > 0) { return $elements->item(0); - } else { - return null; } + + return null; } /** - * Get element attribute + * Get element attribute. * * @param string $attribute - * @param \DOMElement $contextNode * @param string $path - * @return string|null + * + * @return null|string */ - public function getAttribute($attribute, \DOMElement $contextNode = null, $path = null) + public function getAttribute($attribute, ?DOMElement $contextNode = null, $path = null) { $return = null; if ($path !== null) { $elements = $this->getElements($path, $contextNode); if ($elements->length > 0) { - /** @var \DOMElement $node Type hint */ + /** @var DOMElement $node Type hint */ $node = $elements->item(0); $return = $node->getAttribute($attribute); } @@ -148,30 +188,30 @@ public function getAttribute($attribute, \DOMElement $contextNode = null, $path } /** - * Get element value + * Get element value. * * @param string $path - * @param \DOMElement $contextNode - * @return string|null + * + * @return null|string */ - public function getValue($path, \DOMElement $contextNode = null) + public function getValue($path, ?DOMElement $contextNode = null) { $elements = $this->getElements($path, $contextNode); if ($elements->length > 0) { return $elements->item(0)->nodeValue; - } else { - return null; } + + return null; } /** - * Count elements + * Count elements. * * @param string $path - * @param \DOMElement $contextNode - * @return integer + * + * @return int */ - public function countElements($path, \DOMElement $contextNode = null) + public function countElements($path, ?DOMElement $contextNode = null) { $elements = $this->getElements($path, $contextNode); @@ -179,13 +219,13 @@ public function countElements($path, \DOMElement $contextNode = null) } /** - * Element exists + * Element exists. * * @param string $path - * @param \DOMElement $contextNode - * @return boolean + * + * @return bool */ - public function elementExists($path, \DOMElement $contextNode = null) + public function elementExists($path, ?DOMElement $contextNode = null) { return $this->getElements($path, $contextNode)->length > 0; } diff --git a/src/PhpWord/Shared/XMLWriter.php b/src/PhpWord/Shared/XMLWriter.php index 2134f62a9d..441eac05f0 100644 --- a/src/PhpWord/Shared/XMLWriter.php +++ b/src/PhpWord/Shared/XMLWriter.php @@ -1,4 +1,5 @@ xmlWriter = new \XMLWriter(); - // Open temporary storage - if ($tempLocation == self::STORAGE_MEMORY) { - $this->xmlWriter->openMemory(); + if ($pTemporaryStorage == self::STORAGE_MEMORY) { + $this->openMemory(); } else { + if (!$pTemporaryStorageDir || !is_dir($pTemporaryStorageDir)) { + $pTemporaryStorageDir = sys_get_temp_dir(); + } // Create temporary filename - $this->tempFile = tempnam($tempFolder, 'xml'); + $this->tempFileName = @tempnam($pTemporaryStorageDir, 'xml'); - // Fallback to memory when temporary file cannot be used - // @codeCoverageIgnoreStart - // Can't find any test case. Uncomment when found. - if (false === $this->tempFile || false === $this->xmlWriter->openUri($this->tempFile)) { - $this->xmlWriter->openMemory(); - } - // @codeCoverageIgnoreEnd + // Open storage + $this->openUri($this->tempFileName); } - // Set xml Compatibility - $compatibility = Settings::hasCompatibility(); if ($compatibility) { - $this->xmlWriter->setIndent(false); - $this->xmlWriter->setIndentString(''); + $this->setIndent(false); + $this->setIndentString(''); } else { - $this->xmlWriter->setIndent(true); - $this->xmlWriter->setIndentString(' '); + $this->setIndent(true); + $this->setIndentString(' '); } } /** - * Destructor + * Destructor. */ public function __destruct() { - // Destruct XMLWriter - unset($this->xmlWriter); - // Unlink temporary files - if ($this->tempFile != '') { - @unlink($this->tempFile); + if (empty($this->tempFileName)) { + return; } - } - - /** - * Catch function calls (and pass them to internal XMLWriter) - * - * @param mixed $function - * @param mixed $args - * @throws \BadMethodCallException - */ - public function __call($function, $args) - { - // Catch exception - if (method_exists($this->xmlWriter, $function) === false) { - throw new \BadMethodCallException("Method '{$function}' does not exists."); - } - - // Run method - try { - @call_user_func_array(array($this->xmlWriter, $function), $args); - } catch (\Exception $ex) { - // Do nothing! + if (PHP_OS != 'WINNT' && @unlink($this->tempFileName) === false) { + throw new Exception('The file ' . $this->tempFileName . ' could not be deleted.'); } } /** - * Get written data + * Get written data. * - * @return string XML data + * @return string */ public function getData() { - if ($this->tempFile == '') { - return $this->xmlWriter->outputMemory(true); - } else { - $this->xmlWriter->flush(); - return file_get_contents($this->tempFile); + if ($this->tempFileName == '') { + return $this->outputMemory(true); } + + $this->flush(); + + return file_get_contents($this->tempFileName); } /** - * Write simple element and attribute(s) block + * Write simple element and attribute(s) block. * * There are two options: * 1. If the `$attributes` is an array, then it's an associative array of attributes * 2. If not, then it's a simple attribute-value pair * * @param string $element - * @param string|array $attributes + * @param array|string $attributes * @param string $value - * @return void */ - public function writeElementBlock($element, $attributes, $value = null) + public function writeElementBlock($element, $attributes, $value = null): void { - $this->xmlWriter->startElement($element); + $this->startElement($element); if (!is_array($attributes)) { - $attributes = array($attributes => $value); + $attributes = [$attributes => $value]; } foreach ($attributes as $attribute => $value) { - $this->xmlWriter->writeAttribute($attribute, $value); + $this->writeAttribute($attribute, $value); } - $this->xmlWriter->endElement(); + $this->endElement(); } /** @@ -170,17 +141,16 @@ public function writeElementBlock($element, $attributes, $value = null) * @param string $element * @param string $attribute * @param mixed $value - * @return void */ - public function writeElementIf($condition, $element, $attribute = null, $value = null) + public function writeElementIf($condition, $element, $attribute = null, $value = null): void { if ($condition == true) { - if (is_null($attribute)) { - $this->xmlWriter->writeElement($element, $value); + if (null === $attribute) { + $this->writeElement($element, $value); } else { - $this->xmlWriter->startElement($element); - $this->xmlWriter->writeAttribute($attribute, $value); - $this->xmlWriter->endElement(); + $this->startElement($element); + $this->writeAttribute($attribute, $value); + $this->endElement(); } } } @@ -191,12 +161,24 @@ public function writeElementIf($condition, $element, $attribute = null, $value = * @param bool $condition * @param string $attribute * @param mixed $value - * @return void */ - public function writeAttributeIf($condition, $attribute, $value) + public function writeAttributeIf($condition, $attribute, $value): void { if ($condition == true) { - $this->xmlWriter->writeAttribute($attribute, $value); + $this->writeAttribute($attribute, $value); + } + } + + /** + * @param string $name + * @param mixed $value + */ + public function writeAttribute($name, $value): bool + { + if (is_float($value)) { + $value = json_encode($value); } + + return parent::writeAttribute($name, $value ?? ''); } } diff --git a/src/PhpWord/Shared/ZipArchive.php b/src/PhpWord/Shared/ZipArchive.php index 157959e8e9..462206c7f5 100644 --- a/src/PhpWord/Shared/ZipArchive.php +++ b/src/PhpWord/Shared/ZipArchive.php @@ -1,4 +1,5 @@ filename = $filename; + $this->tempDir = Settings::getTempDir(); if (!$this->usePclzip) { $zip = new \ZipArchive(); + + // PHP 8.1 compat - passing null as second arg to \ZipArchive::open() is deprecated + // passing 0 achieves the same behaviour + if ($flags === null) { + $flags = 0; + } + $result = $zip->open($this->filename, $flags); // Scrutizer will report the property numFiles does not exist // See https://github.com/scrutinizer-ci/php-analyzer/issues/190 $this->numFiles = $zip->numFiles; } else { - $zip = new \PclZip($this->filename); - $this->tempDir = Settings::getTempDir(); - $this->numFiles = count($zip->listContent()); + $zip = new PclZip($this->filename); + $zipContent = $zip->listContent(); + $this->numFiles = is_array($zipContent) ? count($zipContent) : 0; } $this->zip = $zip; @@ -148,17 +161,20 @@ public function open($filename, $flags = null) } /** - * Close the active archive + * Close the active archive. * * @return bool - * @throws \PhpOffice\PhpWord\Exception\Exception - * @codeCoverageIgnore Can't find any test case. Uncomment when found. */ public function close() { if (!$this->usePclzip) { - if ($this->zip->close() === false) { - throw new Exception("Could not close zip file {$this->filename}."); + try { + $result = @$this->zip->close(); + } catch (Throwable $e) { + $result = false; + } + if ($result === false) { + throw new Exception("Could not close zip file {$this->filename}: "); } } @@ -166,11 +182,13 @@ public function close() } /** - * Extract the archive contents (emulate \ZipArchive) + * Extract the archive contents (emulate \ZipArchive). * * @param string $destination - * @param string|array $entries + * @param array|string $entries + * * @return bool + * * @since 0.10.0 */ public function extractTo($destination, $entries = null) @@ -181,16 +199,17 @@ public function extractTo($destination, $entries = null) if (!$this->usePclzip) { return $this->zip->extractTo($destination, $entries); - } else { - return $this->pclzipExtractTo($destination, $entries); } + + return $this->pclzipExtractTo($destination, $entries); } /** - * Extract file from archive by given file name (emulate \ZipArchive) + * Extract file from archive by given file name (emulate \ZipArchive). * * @param string $filename Filename for the file in zip archive - * @return string $contents File string contents + * + * @return bool|string $contents File string contents */ public function getFromName($filename) { @@ -208,15 +227,16 @@ public function getFromName($filename) } /** - * Add a new file to the zip archive (emulate \ZipArchive) + * Add a new file to the zip archive (emulate \ZipArchive). * * @param string $filename Directory/Name of the file to add to the zip archive * @param string $localname Directory/Name of the file added to the zip + * * @return bool */ public function pclzipAddFile($filename, $localname = null) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; // Bugfix GH-261 https://github.com/PHPOffice/PHPWord/pull/261 @@ -225,86 +245,101 @@ public function pclzipAddFile($filename, $localname = null) $filename = $realpathFilename; } - $filenameParts = pathinfo($filename); - $localnameParts = pathinfo($localname); + $filenamePartsBaseName = pathinfo($filename, PATHINFO_BASENAME); + $filenamePartsDirName = pathinfo($filename, PATHINFO_DIRNAME); + $localnamePartsBaseName = pathinfo($localname, PATHINFO_BASENAME); + $localnamePartsDirName = pathinfo($localname, PATHINFO_DIRNAME); // To Rename the file while adding it to the zip we // need to create a temp file with the correct name $tempFile = false; - if ($filenameParts['basename'] != $localnameParts['basename']) { + if ($filenamePartsBaseName != $localnamePartsBaseName) { $tempFile = true; // temp file created - $temppath = $this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename']; + $temppath = $this->tempDir . DIRECTORY_SEPARATOR . $localnamePartsBaseName; copy($filename, $temppath); $filename = $temppath; - $filenameParts = pathinfo($temppath); + $filenamePartsDirName = pathinfo($temppath, PATHINFO_DIRNAME); } - $pathRemoved = $filenameParts['dirname']; - $pathAdded = $localnameParts['dirname']; + $pathRemoved = $filenamePartsDirName; + $pathAdded = $localnamePartsDirName; - $res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded); + if (!$this->usePclzip) { + $pathAdded = $pathAdded . '/' . ltrim(str_replace('\\', '/', substr($filename, strlen($pathRemoved))), '/'); + //$res = $zip->addFile($filename, $pathAdded); + $res = $zip->addFromString($pathAdded, file_get_contents($filename)); // addFile can't use subfolders in some cases + } else { + $res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded); + } if ($tempFile) { // Remove temp file, if created - unlink($this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename']); + unlink($this->tempDir . DIRECTORY_SEPARATOR . $localnamePartsBaseName); } - return ($res == 0) ? false : true; + return $res != 0; } /** - * Add a new file to the zip archive from a string of raw data (emulate \ZipArchive) + * Add a new file to the zip archive from a string of raw data (emulate \ZipArchive). * * @param string $localname Directory/Name of the file to add to the zip archive * @param string $contents String of data to add to the zip archive + * * @return bool */ public function pclzipAddFromString($localname, $contents) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; - $filenameParts = pathinfo($localname); + $filenamePartsBaseName = pathinfo($localname, PATHINFO_BASENAME); + $filenamePartsDirName = pathinfo($localname, PATHINFO_DIRNAME); // Write $contents to a temp file - $handle = fopen($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename'], 'wb'); - fwrite($handle, $contents); - fclose($handle); + $handle = fopen($this->tempDir . DIRECTORY_SEPARATOR . $filenamePartsBaseName, 'wb'); + if ($handle) { + fwrite($handle, $contents); + fclose($handle); + } // Add temp file to zip - $filename = $this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename']; + $filename = $this->tempDir . DIRECTORY_SEPARATOR . $filenamePartsBaseName; $pathRemoved = $this->tempDir; - $pathAdded = $filenameParts['dirname']; + $pathAdded = $filenamePartsDirName; $res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded); // Remove temp file - @unlink($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename']); + @unlink($this->tempDir . DIRECTORY_SEPARATOR . $filenamePartsBaseName); - return ($res == 0) ? false : true; + return $res != 0; } /** - * Extract the archive contents (emulate \ZipArchive) + * Extract the archive contents (emulate \ZipArchive). * * @param string $destination - * @param string|array $entries + * @param array|string $entries + * * @return bool + * * @since 0.10.0 */ public function pclzipExtractTo($destination, $entries = null) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; // Extract all files - if (is_null($entries)) { + if (null === $entries) { $result = $zip->extract(PCLZIP_OPT_PATH, $destination); - return ($result > 0) ? true : false; + + return $result > 0; } // Extract by entries if (!is_array($entries)) { - $entries = array($entries); + $entries = [$entries]; } foreach ($entries as $entry) { $entryIndex = $this->locateName($entry); @@ -318,14 +353,15 @@ public function pclzipExtractTo($destination, $entries = null) } /** - * Extract file from archive by given file name (emulate \ZipArchive) + * Extract file from archive by given file name (emulate \ZipArchive). * * @param string $filename Filename for the file in zip archive + * * @return string $contents File string contents */ public function pclzipGetFromName($filename) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; $listIndex = $this->pclzipLocateName($filename); $contents = false; @@ -337,7 +373,7 @@ public function pclzipGetFromName($filename) $listIndex = $this->pclzipLocateName($filename); $extracted = $zip->extractByIndex($listIndex, PCLZIP_OPT_EXTRACT_AS_STRING); } - if ((is_array($extracted)) && ($extracted != 0)) { + if (is_array($extracted) && count($extracted) != 0) { $contents = $extracted[0]['content']; } @@ -345,33 +381,36 @@ public function pclzipGetFromName($filename) } /** - * Returns the name of an entry using its index (emulate \ZipArchive) + * Returns the name of an entry using its index (emulate \ZipArchive). * * @param int $index - * @return string + * + * @return bool|string + * * @since 0.10.0 */ public function pclzipGetNameIndex($index) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; $list = $zip->listContent(); if (isset($list[$index])) { return $list[$index]['filename']; - } else { - return false; } + + return false; } /** - * Returns the index of the entry in the archive (emulate \ZipArchive) + * Returns the index of the entry in the archive (emulate \ZipArchive). * * @param string $filename Filename for the file in zip archive - * @return int + * + * @return false|int */ public function pclzipLocateName($filename) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; $list = $zip->listContent(); $listCount = count($list); @@ -380,10 +419,22 @@ public function pclzipLocateName($filename) if (strtolower($list[$i]['filename']) == strtolower($filename) || strtolower($list[$i]['stored_filename']) == strtolower($filename)) { $listIndex = $i; + break; } } return ($listIndex > -1) ? $listIndex : false; } + + /** + * Add an empty directory to the zip archive (emulate \ZipArchive). + * + * @param string $dirname Directory name to add to the zip archive + */ + public function addEmptyDir(string $dirname): bool + { + // Create a directory entry by adding an empty file with trailing slash + return $this->addFromString(rtrim($dirname, '/') . '/', ''); + } } diff --git a/src/PhpWord/SimpleType/Border.php b/src/PhpWord/SimpleType/Border.php new file mode 100644 index 0000000000..acd1c1a1b1 --- /dev/null +++ b/src/PhpWord/SimpleType/Border.php @@ -0,0 +1,58 @@ +$method(); - } else { - return null; } + + return null; } /** - * Set style value template method + * Set style value template method. * * Some child classes have their own specific overrides. * Backward compability check for versions < 0.10.0 which use underscore @@ -153,7 +162,8 @@ 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 */ public function setStyleValue($key, $value) @@ -161,7 +171,12 @@ public function setStyleValue($key, $value) if (isset($this->aliases[$key])) { $key = $this->aliases[$key]; } - $method = 'set' . String::removeUnderscorePrefix($key); + + if ($key === 'align') { + $key = 'alignment'; + } + + $method = 'set' . Text::removeUnderscorePrefix($key); if (method_exists($this, $method)) { $this->$method($value); } @@ -170,12 +185,13 @@ public function setStyleValue($key, $value) } /** - * Set style by using associative array + * Set style by using associative array. * * @param array $values + * * @return self */ - public function setStyleByArray($values = array()) + public function setStyleByArray($values = []) { foreach ($values as $key => $value) { $this->setStyleValue($key, $value); @@ -185,11 +201,12 @@ public function setStyleByArray($values = array()) } /** - * Set default for null and empty value + * Set default for null and empty value. + * + * @param ?string $value + * @param string $default * - * @param string $value (was: mixed) - * @param string $default (was: mixed) - * @return string (was: mixed) + * @return string */ protected function setNonEmptyVal($value, $default) { @@ -201,10 +218,11 @@ protected function setNonEmptyVal($value, $default) } /** - * Set bool value + * Set bool value. * * @param bool $value * @param bool $default + * * @return bool */ protected function setBoolVal($value, $default) @@ -217,11 +235,12 @@ protected function setBoolVal($value, $default) } /** - * Set numeric value + * Set numeric value. * * @param mixed $value - * @param int|float|null $default - * @return int|float|null + * @param null|float|int $default + * + * @return null|float|int */ protected function setNumericVal($value, $default = null) { @@ -233,37 +252,39 @@ protected function setNumericVal($value, $default = null) } /** - * Set integer value: Convert string that contains only numeric into integer + * Set integer value: Convert string that contains only numeric into integer. * - * @param int|null $value - * @param int|null $default - * @return int|null + * @param null|float|int|string $value + * @param null|int $default + * + * @return null|int */ protected function setIntVal($value, $default = null) { if (is_string($value) && (preg_match('/[^\d]/', $value) == 0)) { - $value = intval($value); + $value = (int) $value; } if (!is_numeric($value)) { $value = $default; } else { - $value = intval($value); + $value = (int) $value; } return $value; } /** - * Set float value: Convert string that contains only numeric into float + * Set float value: Convert string that contains only numeric into float. * * @param mixed $value - * @param float|null $default - * @return float|null + * @param null|float $default + * + * @return null|float */ protected function setFloatVal($value, $default = null) { if (is_string($value) && (preg_match('/[^\d\.\,]/', $value) == 0)) { - $value = floatval($value); + $value = (float) $value; } if (!is_numeric($value)) { $value = $default; @@ -273,18 +294,18 @@ protected function setFloatVal($value, $default = null) } /** - * Set enum value + * Set enum value. * * @param mixed $value * @param array $enum * @param mixed $default + * * @return mixed - * @throws \InvalidArgumentException */ - protected function setEnumVal($value = null, $enum = array(), $default = null) + protected function setEnumVal($value = null, $enum = [], $default = null) { if ($value != null && trim($value) != '' && !empty($enum) && !in_array($value, $enum)) { - throw new \InvalidArgumentException("Invalid style value: {$value} Options:".join(',', $enum)); + throw new InvalidArgumentException("Invalid style value: {$value} Options:" . implode(',', $enum)); } elseif ($value === null || trim($value) == '') { $value = $default; } @@ -293,18 +314,18 @@ protected function setEnumVal($value = null, $enum = array(), $default = null) } /** - * Set object value + * Set object value. * * @param mixed $value - * @param string $styleName * @param mixed &$style + * * @return mixed */ - protected function setObjectVal($value, $styleName, &$style) + protected function setObjectVal($value, string $styleName, &$style) { - $styleClass = substr(get_class($this), 0, strrpos(get_class($this), '\\')) . '\\' . $styleName; + $styleClass = substr(static::class, 0, (int) strrpos(static::class, '\\')) . '\\' . $styleName; if (is_array($value)) { - /** @var \PhpOffice\PhpWord\Style\AbstractStyle $style Type hint */ + /** @var AbstractStyle $style Type hint */ if (!$style instanceof $styleClass) { $style = new $styleClass(); } @@ -317,33 +338,21 @@ protected function setObjectVal($value, $styleName, &$style) } /** - * Set $property value and set $pairProperty = false when $value = true + * Set $property value and set $pairProperty = false when $value = true. * * @param bool &$property * @param bool &$pairProperty * @param bool $value + * * @return self */ protected function setPairedVal(&$property, &$pairProperty, $value) { $property = $this->setBoolVal($value, $property); - if ($value == true) { + if ($value === true) { $pairProperty = false; } return $this; } - - /** - * Set style using associative array - * - * @param array $style - * @return self - * @deprecated 0.11.0 - * @codeCoverageIgnore - */ - public function setArrayStyle(array $style = array()) - { - return $this->setStyleByArray($style); - } } diff --git a/src/PhpWord/Style/Alignment.php b/src/PhpWord/Style/Alignment.php deleted file mode 100644 index 3beabe376a..0000000000 --- a/src/PhpWord/Style/Alignment.php +++ /dev/null @@ -1,78 +0,0 @@ -setStyleByArray($style); - } - - /** - * Get alignment - * - * @return string - */ - public function getValue() - { - return $this->value; - } - - /** - * Set alignment - * - * @param string $value - * @return self - */ - public function setValue($value = null) - { - if (strtolower($value) == self::ALIGN_JUSTIFY) { - $value = self::ALIGN_BOTH; - } - $enum = array(self::ALIGN_LEFT, self::ALIGN_RIGHT, self::ALIGN_CENTER, self::ALIGN_BOTH, self::ALIGN_JUSTIFY); - $this->value = $this->setEnumVal($value, $enum, $this->value); - - return $this; - } -} diff --git a/src/PhpWord/Style/Border.php b/src/PhpWord/Style/Border.php index 84116d7a70..722e09931a 100644 --- a/src/PhpWord/Style/Border.php +++ b/src/PhpWord/Style/Border.php @@ -1,4 +1,5 @@ getBorderTopSize(), $this->getBorderLeftSize(), $this->getBorderRightSize(), $this->getBorderBottomSize(), - ); + ]; } /** - * Set border size + * Set border size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setBorderSize($value = null) @@ -110,24 +170,25 @@ public function setBorderSize($value = null) } /** - * Get border color + * Get border color. * - * @return string[] + * @return array */ public function getBorderColor() { - return array( + return [ $this->getBorderTopColor(), $this->getBorderLeftColor(), $this->getBorderRightColor(), $this->getBorderBottomColor(), - ); + ]; } /** - * Set border color + * Set border color. + * + * @param null|string $value * - * @param string $value * @return self */ public function setBorderColor($value = null) @@ -141,9 +202,41 @@ public function setBorderColor($value = null) } /** - * Get border top size + * Get border style. * - * @return int|float + * @return string[] + */ + public function getBorderStyle() + { + return [ + $this->getBorderTopStyle(), + $this->getBorderLeftStyle(), + $this->getBorderRightStyle(), + $this->getBorderBottomStyle(), + ]; + } + + /** + * Set border style. + * + * @param string $value + * + * @return self + */ + public function setBorderStyle($value = null) + { + $this->setBorderTopStyle($value); + $this->setBorderLeftStyle($value); + $this->setBorderRightStyle($value); + $this->setBorderBottomStyle($value); + + return $this; + } + + /** + * Get border top size. + * + * @return float|int */ public function getBorderTopSize() { @@ -151,9 +244,10 @@ public function getBorderTopSize() } /** - * Set border top size + * Set border top size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setBorderTopSize($value = null) @@ -164,9 +258,9 @@ public function setBorderTopSize($value = null) } /** - * Get border top color + * Get border top color. * - * @return string + * @return null|string */ public function getBorderTopColor() { @@ -174,9 +268,10 @@ public function getBorderTopColor() } /** - * Set border top color + * Set border top color. + * + * @param null|string $value * - * @param string $value * @return self */ public function setBorderTopColor($value = null) @@ -187,9 +282,33 @@ public function setBorderTopColor($value = null) } /** - * Get border left size + * Get border top style. + * + * @return string + */ + public function getBorderTopStyle() + { + return $this->borderTopStyle; + } + + /** + * Set border top Style. + * + * @param string $value + * + * @return self + */ + public function setBorderTopStyle($value = null) + { + $this->borderTopStyle = $value; + + return $this; + } + + /** + * Get border left size. * - * @return int|float + * @return float|int */ public function getBorderLeftSize() { @@ -197,9 +316,10 @@ public function getBorderLeftSize() } /** - * Set border left size + * Set border left size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setBorderLeftSize($value = null) @@ -210,9 +330,9 @@ public function setBorderLeftSize($value = null) } /** - * Get border left color + * Get border left color. * - * @return string + * @return null|string */ public function getBorderLeftColor() { @@ -220,9 +340,10 @@ public function getBorderLeftColor() } /** - * Set border left color + * Set border left color. + * + * @param null|string $value * - * @param string $value * @return self */ public function setBorderLeftColor($value = null) @@ -233,9 +354,33 @@ public function setBorderLeftColor($value = null) } /** - * Get border right size + * Get border left style. + * + * @return string + */ + public function getBorderLeftStyle() + { + return $this->borderLeftStyle; + } + + /** + * Set border left style. + * + * @param string $value + * + * @return self + */ + public function setBorderLeftStyle($value = null) + { + $this->borderLeftStyle = $value; + + return $this; + } + + /** + * Get border right size. * - * @return int|float + * @return float|int */ public function getBorderRightSize() { @@ -243,9 +388,10 @@ public function getBorderRightSize() } /** - * Set border right size + * Set border right size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setBorderRightSize($value = null) @@ -256,9 +402,9 @@ public function setBorderRightSize($value = null) } /** - * Get border right color + * Get border right color. * - * @return string + * @return null|string */ public function getBorderRightColor() { @@ -266,9 +412,10 @@ public function getBorderRightColor() } /** - * Set border right color + * Set border right color. + * + * @param null|string $value * - * @param string $value * @return self */ public function setBorderRightColor($value = null) @@ -279,9 +426,33 @@ public function setBorderRightColor($value = null) } /** - * Get border bottom size + * Get border right style. + * + * @return string + */ + public function getBorderRightStyle() + { + return $this->borderRightStyle; + } + + /** + * Set border right style. * - * @return int|float + * @param string $value + * + * @return self + */ + public function setBorderRightStyle($value = null) + { + $this->borderRightStyle = $value; + + return $this; + } + + /** + * Get border bottom size. + * + * @return float|int */ public function getBorderBottomSize() { @@ -289,9 +460,10 @@ public function getBorderBottomSize() } /** - * Set border bottom size + * Set border bottom size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setBorderBottomSize($value = null) @@ -302,9 +474,9 @@ public function setBorderBottomSize($value = null) } /** - * Get border bottom color + * Get border bottom color. * - * @return string + * @return null|string */ public function getBorderBottomColor() { @@ -312,9 +484,10 @@ public function getBorderBottomColor() } /** - * Set border bottom color + * Set border bottom color. + * + * @param null|string $value * - * @param string $value * @return self */ public function setBorderBottomColor($value = null) @@ -325,7 +498,31 @@ public function setBorderBottomColor($value = null) } /** - * Check if any of the border is not null + * Get border bottom style. + * + * @return string + */ + public function getBorderBottomStyle() + { + return $this->borderBottomStyle; + } + + /** + * Set border bottom style. + * + * @param string $value + * + * @return self + */ + public function setBorderBottomStyle($value = null) + { + $this->borderBottomStyle = $value; + + return $this; + } + + /** + * Check if any of the border is not null. * * @return bool */ @@ -335,4 +532,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 11290c8752..e2b59f417d 100644 --- a/src/PhpWord/Style/Cell.php +++ b/src/PhpWord/Style/Cell.php @@ -1,4 +1,5 @@ vAlign = $this->setEnumVal($value, $enum, $this->vAlign); + if ($value === null) { + $this->vAlign = null; + + return $this; + } + + VerticalJc::validate($value); + $this->vAlign = $this->setEnumVal($value, VerticalJc::values(), $this->vAlign); return $this; } @@ -128,21 +188,29 @@ public function getTextDirection() } /** - * Set text direction + * Set text direction. * * @param string $value + * * @return self */ public function setTextDirection($value = null) { - $enum = array(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; } /** - * Get background + * Get background. * * @return string */ @@ -150,26 +218,27 @@ public function getBgColor() { if ($this->shading !== null) { return $this->shading->getFill(); - } else { - return null; } + + return null; } /** - * Set background + * Set background. * * @param string $value + * * @return self */ public function setBgColor($value = null) { - return $this->setShading(array('fill' => $value)); + return $this->setShading(['fill' => $value]); } /** * Get grid span (colspan). * - * @return integer + * @return int */ public function getGridSpan() { @@ -177,9 +246,10 @@ public function getGridSpan() } /** - * Set grid span (colspan) + * Set grid span (colspan). * * @param int $value + * * @return self */ public function setGridSpan($value = null) @@ -192,7 +262,7 @@ public function setGridSpan($value = null) /** * Get vertical merge (rowspan). * - * @return string + * @return null|string */ public function getVMerge() { @@ -200,23 +270,30 @@ public function getVMerge() } /** - * Set vertical merge (rowspan) + * Set vertical merge (rowspan). + * + * @param null|string $value * - * @param string $value * @return self */ public function setVMerge($value = null) { - $enum = array(self::VMERGE_RESTART, self::VMERGE_CONTINUE); + if ($value === null) { + $this->vMerge = null; + + return $this; + } + + $enum = [self::VMERGE_RESTART, self::VMERGE_CONTINUE]; $this->vMerge = $this->setEnumVal($value, $enum, $this->vMerge); return $this; } /** - * Get shading + * Get shading. * - * @return \PhpOffice\PhpWord\Style\Shading + * @return Shading */ public function getShading() { @@ -224,9 +301,10 @@ public function getShading() } /** - * Set shading + * Set shading. * * @param mixed $value + * * @return self */ public function setShading($value = null) @@ -237,13 +315,146 @@ public function setShading($value = null) } /** - * Get default border color + * Get cell width. * - * @deprecated 0.10.0 - * @codeCoverageIgnore + * @return ?int */ - public function getDefaultBorderColor() + public function getWidth() { - return self::DEFAULT_BORDER_COLOR; + return $this->width; + } + + /** + * Set cell width. + * + * @param int $value + * + * @return self + */ + public function setWidth($value) + { + $this->width = $this->setIntVal($value); + + return $this; + } + + /** + * Get width unit. + * + * @return string + */ + public function getUnit() + { + return $this->unit; + } + + /** + * Set width unit. + * + * @param string $value + */ + public function setUnit($value) + { + $this->unit = $this->setEnumVal($value, [TblWidth::AUTO, TblWidth::PERCENT, TblWidth::TWIP], TblWidth::TWIP); + + 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; + } + + /** + * Get style padding-top. + */ + public function getPaddingTop(): ?int + { + return $this->paddingTop; + } + + /** + * Set style padding-top. + * + * @return $this + */ + public function setPaddingTop(int $value): self + { + $this->paddingTop = $value; + + return $this; + } + + /** + * Get style padding-bottom. + */ + public function getPaddingBottom(): ?int + { + return $this->paddingBottom; + } + + /** + * Set style padding-bottom. + * + * @return $this + */ + public function setPaddingBottom(int $value): self + { + $this->paddingBottom = $value; + + return $this; + } + + /** + * Get style padding-left. + */ + public function getPaddingLeft(): ?int + { + return $this->paddingLeft; + } + + /** + * Set style padding-left. + * + * @return $this + */ + public function setPaddingLeft(int $value): self + { + $this->paddingLeft = $value; + + return $this; + } + + /** + * Get style padding-right. + */ + public function getPaddingRight(): ?int + { + return $this->paddingRight; + } + + /** + * Set style padding-right. + * + * @return $this + */ + public function setPaddingRight(int $value): self + { + $this->paddingRight = $value; + + return $this; } } diff --git a/src/PhpWord/Style/Chart.php b/src/PhpWord/Style/Chart.php index 13b72a33f5..3773565ce9 100644 --- a/src/PhpWord/Style/Chart.php +++ b/src/PhpWord/Style/Chart.php @@ -1,4 +1,5 @@ true, // value + 'showCatName' => true, // category name + 'showLegendKey' => false, //show the cart legend + 'showSerName' => false, // series name + 'showPercent' => false, + 'showLeaderLines' => false, + 'showBubbleSize' => false, + ]; + + /** + * A string that tells the writer where to write chart labels or to skip + * "nextTo" - sets labels next to the axis (bar graphs on the left) (default) + * "low" - labels on the left side of the graph + * "high" - labels on the right side of the graph. + * + * @var string + */ + private $categoryLabelPosition = 'nextTo'; + + /** + * A string that tells the writer where to write chart labels or to skip + * "nextTo" - sets labels next to the axis (bar graphs on the bottom) (default) + * "low" - labels are below the graph + * "high" - labels above the graph. + * + * @var string + */ + private $valueLabelPosition = 'nextTo'; + + /** + * @var string + */ + private $categoryAxisTitle; + + /** + * @var string + */ + private $valueAxisTitle; + + /** + * The position for major tick marks + * Possible values are 'in', 'out', 'cross', 'none'. + * + * @var string + */ + private $majorTickMarkPos = 'none'; + + /** + * Show labels for axis. + * + * @var bool + */ + private $showAxisLabels = false; + + /** + * Show Gridlines for Y-Axis. + * + * @var bool + */ + private $gridY = false; + + /** + * Show Gridlines for X-Axis. + * + * @var bool + */ + private $gridX = false; + + /** + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get width + * Get width. * * @return int */ @@ -67,9 +170,10 @@ public function getWidth() } /** - * Set width + * Set width. * * @param int $value + * * @return self */ public function setWidth($value = null) @@ -80,7 +184,7 @@ public function setWidth($value = null) } /** - * Get height + * Get height. * * @return int */ @@ -90,9 +194,10 @@ public function getHeight() } /** - * Set height + * Set height. * * @param int $value + * * @return self */ public function setHeight($value = null) @@ -103,7 +208,7 @@ public function setHeight($value = null) } /** - * Is 3D + * Is 3D. * * @return bool */ @@ -113,9 +218,10 @@ public function is3d() } /** - * Set 3D + * Set 3D. * * @param bool $value + * * @return self */ public function set3d($value = true) @@ -124,4 +230,322 @@ public function set3d($value = true) return $this; } + + /** + * Get the list of colors to use in a chart. + * + * @return array + */ + public function getColors() + { + return $this->colors; + } + + /** + * Set the colors to use in a chart. + * + * @param array $value a list of colors to use in the chart + * + * @return self + */ + public function setColors($value = []) + { + $this->colors = $value; + + return $this; + } + + /** + * Get the chart title. + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set the chart title. + * + * @param string $value + * + * @return self + */ + public function setTitle($value = null) + { + $this->title = $value; + + return $this; + } + + /** + * Get chart legend visibility. + * + * @return bool + */ + public function isShowLegend() + { + return $this->showLegend; + } + + /** + * Set chart legend visibility. + * + * @param bool $value + * + * @return self + */ + public function setShowLegend($value = false) + { + $this->showLegend = $value; + + return $this; + } + + /** + * Get chart legend position. + * + * @return string + */ + public function getLegendPosition() + { + return $this->legendPosition; + } + + /** + * Set chart legend position. choices: + * "r" - right of chart + * "b" - bottom of chart + * "t" - top of chart + * "l" - left of chart + * "tr" - top right of chart. + * + * default: right + * + * @param string $legendPosition + * + * @return self + */ + public function setLegendPosition($legendPosition = 'r') + { + $enum = ['r', 'b', 't', 'l', 'tr']; + $this->legendPosition = $this->setEnumVal($legendPosition, $enum, $this->legendPosition); + + return $this; + } + + /* + * Show labels for axis + * + * @return bool + */ + public function showAxisLabels() + { + return $this->showAxisLabels; + } + + /** + * Set show Gridlines for Y-Axis. + * + * @param bool $value + * + * @return self + */ + public function setShowAxisLabels($value = true) + { + $this->showAxisLabels = $this->setBoolVal($value, $this->showAxisLabels); + + return $this; + } + + /** + * get the list of options for data labels. + * + * @return array + */ + public function getDataLabelOptions() + { + return $this->dataLabelOptions; + } + + /** + * Set values for data label options. + * This will only change values for options defined in $this->dataLabelOptions, and cannot create new ones. + * + * @param array $values [description] + */ + public function setDataLabelOptions($values = []): void + { + foreach (array_keys($this->dataLabelOptions) as $option) { + if (isset($values[$option])) { + $this->dataLabelOptions[$option] = $this->setBoolVal( + $values[$option], + $this->dataLabelOptions[$option] + ); + } + } + } + + /* + * Show Gridlines for Y-Axis + * + * @return bool + */ + public function showGridY() + { + return $this->gridY; + } + + /** + * Set show Gridlines for Y-Axis. + * + * @param bool $value + * + * @return self + */ + public function setShowGridY($value = true) + { + $this->gridY = $this->setBoolVal($value, $this->gridY); + + return $this; + } + + /** + * Get the categoryLabelPosition setting. + * + * @return string + */ + public function getCategoryLabelPosition() + { + return $this->categoryLabelPosition; + } + + /** + * Set the categoryLabelPosition setting + * "none" - skips writing labels + * "nextTo" - sets labels next to the (bar graphs on the left) + * "low" - labels on the left side of the graph + * "high" - labels on the right side of the graph. + * + * @param mixed $labelPosition + * + * @return self + */ + public function setCategoryLabelPosition($labelPosition) + { + $enum = ['nextTo', 'low', 'high']; + $this->categoryLabelPosition = $this->setEnumVal($labelPosition, $enum, $this->categoryLabelPosition); + + return $this; + } + + /** + * Get the valueAxisLabelPosition setting. + * + * @return string + */ + public function getValueLabelPosition() + { + return $this->valueLabelPosition; + } + + /** + * Set the valueLabelPosition setting + * "none" - skips writing labels + * "nextTo" - sets labels next to the value + * "low" - sets labels are below the graph + * "high" - sets labels above the graph. + */ + public function setValueLabelPosition(string $labelPosition) + { + $enum = ['nextTo', 'low', 'high']; + $this->valueLabelPosition = $this->setEnumVal($labelPosition, $enum, $this->valueLabelPosition); + + return $this; + } + + /** + * Get the categoryAxisTitle. + * + * @return string + */ + public function getCategoryAxisTitle() + { + return $this->categoryAxisTitle; + } + + /** + * Set the title that appears on the category side of the chart. + * + * @param string $axisTitle + */ + public function setCategoryAxisTitle($axisTitle) + { + $this->categoryAxisTitle = $axisTitle; + + return $this; + } + + /** + * Get the valueAxisTitle. + * + * @return string + */ + public function getValueAxisTitle() + { + return $this->valueAxisTitle; + } + + /** + * Set the title that appears on the value side of the chart. + * + * @param string $axisTitle + */ + public function setValueAxisTitle($axisTitle) + { + $this->valueAxisTitle = $axisTitle; + + return $this; + } + + public function getMajorTickPosition() + { + return $this->majorTickMarkPos; + } + + /** + * Set the position for major tick marks. + * + * @param string $position + */ + public function setMajorTickPosition($position): void + { + $enum = ['in', 'out', 'cross', 'none']; + $this->majorTickMarkPos = $this->setEnumVal($position, $enum, $this->majorTickMarkPos); + } + + /** + * Show Gridlines for X-Axis. + * + * @return bool + */ + public function showGridX() + { + return $this->gridX; + } + + /** + * Set show Gridlines for X-Axis. + * + * @param bool $value + * + * @return self + */ + public function setShowGridX($value = true) + { + $this->gridX = $this->setBoolVal($value, $this->gridX); + + return $this; + } } diff --git a/src/PhpWord/Style/Extrusion.php b/src/PhpWord/Style/Extrusion.php index ccbb26505b..c31c51eb4f 100644 --- a/src/PhpWord/Style/Extrusion.php +++ b/src/PhpWord/Style/Extrusion.php @@ -1,4 +1,5 @@ setStyleByArray($style); } /** - * Get type + * Get type. * * @return string */ @@ -68,21 +69,22 @@ public function getType() } /** - * Set pattern + * Set pattern. * * @param string $value + * * @return self */ public function setType($value = null) { - $enum = array(self::EXTRUSION_PARALLEL, self::EXTRUSION_PERSPECTIVE); + $enum = [self::EXTRUSION_PARALLEL, self::EXTRUSION_PERSPECTIVE]; $this->type = $this->setEnumVal($value, $enum, null); return $this; } /** - * Get color + * Get color. * * @return string */ @@ -92,9 +94,10 @@ public function getColor() } /** - * Set color + * Set color. * * @param string $value + * * @return self */ public function setColor($value = null) diff --git a/src/PhpWord/Style/Fill.php b/src/PhpWord/Style/Fill.php index 08c7a85739..f9dfe5a1cb 100644 --- a/src/PhpWord/Style/Fill.php +++ b/src/PhpWord/Style/Fill.php @@ -1,4 +1,5 @@ setStyleByArray($style); } /** - * Get color + * Get color. * * @return string */ @@ -55,9 +56,10 @@ public function getColor() } /** - * Set color + * Set color. * * @param string $value + * * @return self */ public function setColor($value = null) diff --git a/src/PhpWord/Style/Font.php b/src/PhpWord/Style/Font.php index 8980258b43..f03e8899d1 100644 --- a/src/PhpWord/Style/Font.php +++ b/src/PhpWord/Style/Font.php @@ -1,4 +1,5 @@ 'lineHeight'); + protected $aliases = ['line-height' => 'lineHeight', 'letter-spacing' => 'spacing']; /** - * Font style type + * Font style type. * * @var string */ private $type; /** - * Font name + * Font name. * * @var string */ private $name; /** - * Font Content Type + * Font Content Type. * * @var string */ private $hint; /** - * Font size + * Font size. * - * @var int|float + * @var float|int */ private $size; /** - * Font color + * Font color. * - * @var string + * @var null|string */ private $color; /** - * Bold + * Bold. * * @var bool */ - private $bold = false; + private $bold; /** - * Italic + * Italic. * * @var bool */ - private $italic = false; + private $italic; /** - * Undeline + * Undeline. * * @var string */ private $underline = self::UNDERLINE_NONE; /** - * Superscript + * Superscript. * * @var bool */ private $superScript = false; /** - * Subscript + * Subscript. * * @var bool */ private $subScript = false; /** - * Strikethrough + * Strikethrough. * * @var bool */ - private $strikethrough = false; + private $strikethrough; /** - * Double strikethrough + * Double strikethrough. * * @var bool */ - private $doubleStrikethrough = false; + private $doubleStrikethrough; /** - * Small caps + * Small caps. * * @var bool - * @link http://www.schemacentral.com/sc/ooxml/e-w_smallCaps-1.html + * + * @see http://www.schemacentral.com/sc/ooxml/e-w_smallCaps-1.html */ - private $smallCaps = false; + private $smallCaps; /** - * All caps + * All caps. * * @var bool - * @link http://www.schemacentral.com/sc/ooxml/e-w_caps-1.html + * + * @see http://www.schemacentral.com/sc/ooxml/e-w_caps-1.html */ - private $allCaps = false; + private $allCaps; /** - * Foreground/highlight + * Foreground/highlight. * * @var string */ private $fgColor; /** - * Expanded/compressed text: 0-600 (percent) + * Expanded/compressed text: 0-600 (percent). * * @var int + * * @since 0.12.0 - * @link http://www.schemacentral.com/sc/ooxml/e-w_w-1.html + * @see http://www.schemacentral.com/sc/ooxml/e-w_w-1.html */ private $scale; /** - * Character spacing adjustment: twip + * Character spacing adjustment: twip. + * + * @var float|int * - * @var int|float * @since 0.12.0 - * @link http://www.schemacentral.com/sc/ooxml/e-w_spacing-2.html + * @see http://www.schemacentral.com/sc/ooxml/e-w_spacing-2.html */ private $spacing; /** - * Font kerning: halfpoint + * Font kerning: halfpoint. + * + * @var float|int * - * @var int|float * @since 0.12.0 - * @link http://www.schemacentral.com/sc/ooxml/e-w_kern-1.html + * @see http://www.schemacentral.com/sc/ooxml/e-w_kern-1.html */ private $kerning; /** - * Paragraph style + * Paragraph style. * - * @var \PhpOffice\PhpWord\Style\Paragraph + * @var Paragraph */ private $paragraph; /** - * Shading + * Shading. * - * @var \PhpOffice\PhpWord\Style\Shading + * @var Shading */ private $shading; /** - * Right to left languages - * @var boolean + * Right to left languages. + * + * @var ?bool + */ + private $rtl; + + /** + * noProof (disables AutoCorrect). + * + * @var bool + * http://www.datypic.com/sc/ooxml/e-w_noProof-1.html + */ + private $noProof; + + /** + * Languages. + * + * @var null|Language + */ + private $lang; + + /** + * Hidden text. + * + * @var bool + * + * @see http://www.datypic.com/sc/ooxml/e-w_vanish-1.html + */ + private $hidden; + + /** + * Vertically Raised or Lowered Text. + * + * @var int Signed Half-Point Measurement + * + * @see http://www.datypic.com/sc/ooxml/e-w_position-1.html + */ + private $position; + + /** + * Preservation of white space in html. + * + * @var string Value used for css white-space */ - private $rtl = false; + private $whiteSpace = ''; /** - * Create new font style + * Generic font as fallback for html. + * + * @var string generic font name + */ + private $fallbackFont = ''; + + /** + * Create new font style. * * @param string $type Type of font - * @param array $paragraph Paragraph styles definition + * @param AbstractStyle|array|string $paragraph Paragraph styles definition */ public function __construct($type = 'text', $paragraph = null) { @@ -241,48 +298,52 @@ public function __construct($type = 'text', $paragraph = null) } /** - * Get style values + * Get style values. * * @return array + * * @since 0.12.0 */ public function getStyleValues() { - $styles = array( - 'name' => $this->getStyleName(), - 'basic' => array( - 'name' => $this->getName(), - 'size' => $this->getSize(), - 'color' => $this->getColor(), - 'hint' => $this->getHint(), - ), - 'style' => array( - 'bold' => $this->isBold(), - 'italic' => $this->isItalic(), + return [ + 'name' => $this->getStyleName(), + 'basic' => [ + 'name' => $this->getName(), + 'size' => $this->getSize(), + 'color' => $this->getColor(), + 'hint' => $this->getHint(), + ], + 'style' => [ + 'bold' => $this->isBold(), + 'italic' => $this->isItalic(), 'underline' => $this->getUnderline(), - 'strike' => $this->isStrikethrough(), - 'dStrike' => $this->isDoubleStrikethrough(), - 'super' => $this->isSuperScript(), - 'sub' => $this->isSubScript(), + 'strike' => $this->isStrikethrough(), + 'dStrike' => $this->isDoubleStrikethrough(), + 'super' => $this->isSuperScript(), + 'sub' => $this->isSubScript(), 'smallCaps' => $this->isSmallCaps(), - 'allCaps' => $this->isAllCaps(), - 'fgColor' => $this->getFgColor(), - ), - 'spacing' => array( - 'scale' => $this->getScale(), - 'spacing' => $this->getSpacing(), - 'kerning' => $this->getKerning(), - ), - 'paragraph' => $this->getParagraph(), - 'rtl' => $this->isRTL(), - 'shading' => $this->getShading(), - ); - - return $styles; - } - - /** - * Get style type + 'allCaps' => $this->isAllCaps(), + 'fgColor' => $this->getFgColor(), + 'hidden' => $this->isHidden(), + ], + 'spacing' => [ + 'scale' => $this->getScale(), + 'spacing' => $this->getSpacing(), + 'kerning' => $this->getKerning(), + 'position' => $this->getPosition(), + ], + 'paragraph' => $this->getParagraph(), + 'rtl' => $this->isRTL(), + 'shading' => $this->getShading(), + 'lang' => $this->getLang(), + 'whiteSpace' => $this->getWhiteSpace(), + 'fallbackFont' => $this->getFallbackFont(), + ]; + } + + /** + * Get style type. * * @return string */ @@ -292,7 +353,7 @@ public function getStyleType() } /** - * Get font name + * Get font name. * * @return string */ @@ -302,9 +363,10 @@ public function getName() } /** - * Set font name + * Set font name. * * @param string $value + * * @return self */ public function setName($value = null) @@ -315,7 +377,7 @@ public function setName($value = null) } /** - * Get Font Content Type + * Get Font Content Type. * * @return string */ @@ -325,9 +387,10 @@ public function getHint() } /** - * Set Font Content Type + * Set Font Content Type. * * @param string $value + * * @return self */ public function setHint($value = null) @@ -338,9 +401,9 @@ public function setHint($value = null) } /** - * Get font size + * Get font size. * - * @return int|float + * @return float|int */ public function getSize() { @@ -348,9 +411,10 @@ public function getSize() } /** - * Set font size + * Set font size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setSize($value = null) @@ -361,19 +425,18 @@ public function setSize($value = null) } /** - * Get font color - * - * @return string + * Get font color. */ - public function getColor() + public function getColor(): ?string { return $this->color; } /** - * Set font color + * Set font color. * * @param string $value + * * @return self */ public function setColor($value = null) @@ -384,7 +447,7 @@ public function setColor($value = null) } /** - * Get bold + * Get bold. * * @return bool */ @@ -394,9 +457,10 @@ public function isBold() } /** - * Set bold + * Set bold. * * @param bool $value + * * @return self */ public function setBold($value = true) @@ -407,7 +471,7 @@ public function setBold($value = true) } /** - * Get italic + * Get italic. * * @return bool */ @@ -417,9 +481,10 @@ public function isItalic() } /** - * Set italic + * Set italic. * * @param bool $value + * * @return self */ public function setItalic($value = true) @@ -430,7 +495,7 @@ public function setItalic($value = true) } /** - * Get underline + * Get underline. * * @return string */ @@ -440,9 +505,10 @@ public function getUnderline() } /** - * Set underline + * Set underline. * * @param string $value + * * @return self */ public function setUnderline($value = self::UNDERLINE_NONE) @@ -453,7 +519,7 @@ public function setUnderline($value = self::UNDERLINE_NONE) } /** - * Get superscript + * Get superscript. * * @return bool */ @@ -463,9 +529,10 @@ public function isSuperScript() } /** - * Set superscript + * Set superscript. * * @param bool $value + * * @return self */ public function setSuperScript($value = true) @@ -474,7 +541,7 @@ public function setSuperScript($value = true) } /** - * Get subscript + * Get subscript. * * @return bool */ @@ -484,9 +551,10 @@ public function isSubScript() } /** - * Set subscript + * Set subscript. * * @param bool $value + * * @return self */ public function setSubScript($value = true) @@ -495,49 +563,43 @@ public function setSubScript($value = true) } /** - * Get strikethrough - * - * @return bool + * Get strikethrough. */ - public function isStrikethrough() + public function isStrikethrough(): ?bool { return $this->strikethrough; } /** - * Set strikethrough + * 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 + * Get double strikethrough. */ - public function isDoubleStrikethrough() + public function isDoubleStrikethrough(): ?bool { return $this->doubleStrikethrough; } /** - * Set double strikethrough + * 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); } /** - * Get small caps + * Get small caps. * * @return bool */ @@ -547,9 +609,10 @@ public function isSmallCaps() } /** - * Set small caps + * Set small caps. * * @param bool $value + * * @return self */ public function setSmallCaps($value = true) @@ -558,7 +621,7 @@ public function setSmallCaps($value = true) } /** - * Get all caps + * Get all caps. * * @return bool */ @@ -568,9 +631,10 @@ public function isAllCaps() } /** - * Set all caps + * Set all caps. * * @param bool $value + * * @return self */ public function setAllCaps($value = true) @@ -579,7 +643,7 @@ public function setAllCaps($value = true) } /** - * Get foreground/highlight color + * Get foreground/highlight color. * * @return string */ @@ -589,9 +653,10 @@ public function getFgColor() } /** - * Set foreground/highlight color + * Set foreground/highlight color. * * @param string $value + * * @return self */ public function setFgColor($value = null) @@ -602,7 +667,7 @@ public function setFgColor($value = null) } /** - * Get background + * Get background. * * @return string */ @@ -612,18 +677,19 @@ public function getBgColor() } /** - * Set background + * Set background. * * @param string $value - * @return \PhpOffice\PhpWord\Style\Table + * + * @return Table */ public function setBgColor($value = null) { - $this->setShading(array('fill' => $value)); + $this->setShading(['fill' => $value]); } /** - * Get scale + * Get scale. * * @return int */ @@ -633,9 +699,10 @@ public function getScale() } /** - * Set scale + * Set scale. * * @param int $value + * * @return self */ public function setScale($value = null) @@ -646,9 +713,9 @@ public function setScale($value = null) } /** - * Get font spacing + * Get font spacing. * - * @return int|float + * @return float|int */ public function getSpacing() { @@ -656,9 +723,10 @@ public function getSpacing() } /** - * Set font spacing + * Set font spacing. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setSpacing($value = null) @@ -669,9 +737,9 @@ public function setSpacing($value = null) } /** - * Get font kerning + * Get font kerning. * - * @return int|float + * @return float|int */ public function getKerning() { @@ -679,9 +747,10 @@ public function getKerning() } /** - * Set font kerning + * Set font kerning. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setKerning($value = null) @@ -692,9 +761,33 @@ public function setKerning($value = null) } /** - * Get line height + * Get noProof (disables autocorrect). * - * @return int|float + * @return bool + */ + public function isNoProof() + { + return $this->noProof; + } + + /** + * Set noProof (disables autocorrect). + * + * @param bool $value + * + * @return $this + */ + public function setNoProof($value = false) + { + $this->noProof = $value; + + return $this; + } + + /** + * Get line height. + * + * @return float|int */ public function getLineHeight() { @@ -702,22 +795,23 @@ public function getLineHeight() } /** - * Set lineheight + * Set lineheight. + * + * @param float|int|string $value * - * @param int|float|string $value * @return self */ public function setLineHeight($value) { - $this->setParagraph(array('lineHeight' => $value)); + $this->setParagraph(['lineHeight' => $value]); return $this; } /** - * Get paragraph style + * Get paragraph style. * - * @return \PhpOffice\PhpWord\Style\Paragraph + * @return Paragraph */ public function getParagraph() { @@ -725,9 +819,10 @@ public function getParagraph() } /** - * Set shading + * Set Paragraph. * * @param mixed $value + * * @return self */ public function setParagraph($value = null) @@ -738,19 +833,20 @@ public function setParagraph($value = null) } /** - * Get rtl + * Get rtl. * - * @return bool + * @return ?bool */ public function isRTL() { - return $this->rtl; + return $this->rtl ?? Settings::isDefaultRtl(); } /** - * Set rtl + * Set rtl. + * + * @param ?bool $value * - * @param bool $value * @return self */ public function setRTL($value = true) @@ -761,9 +857,9 @@ public function setRTL($value = true) } /** - * Get shading + * Get shading. * - * @return \PhpOffice\PhpWord\Style\Shading + * @return Shading */ public function getShading() { @@ -771,9 +867,10 @@ public function getShading() } /** - * Set shading + * Set shading. * * @param mixed $value + * * @return self */ public function setShading($value = null) @@ -784,68 +881,117 @@ public function setShading($value = null) } /** - * Get bold + * Get language. + * + * @return null|Language + */ + public function getLang() + { + return $this->lang; + } + + /** + * Set language. + * + * @param mixed $value + * + * @return self + */ + public function setLang($value = null) + { + if (is_string($value) && $value != '') { + $value = new Language($value); + } + $this->setObjectVal($value, 'Language', $this->lang); + + return $this; + } + + /** + * Get hidden text. * - * @deprecated 0.10.0 - * @codeCoverageIgnore + * @return bool */ - public function getBold() + public function isHidden() { - return $this->isBold(); + return $this->hidden; } /** - * Get italic + * Set hidden text. * - * @deprecated 0.10.0 - * @codeCoverageIgnore + * @param bool $value + * + * @return self */ - public function getItalic() + public function setHidden($value = true) { - return $this->isItalic(); + $this->hidden = $this->setBoolVal($value, $this->hidden); + + return $this; } /** - * Get superscript + * Get position. * - * @deprecated 0.10.0 - * @codeCoverageIgnore + * @return int */ - public function getSuperScript() + public function getPosition() { - return $this->isSuperScript(); + return $this->position; } /** - * Get subscript + * Set position. + * + * @param int $value * - * @deprecated 0.10.0 - * @codeCoverageIgnore + * @return self */ - public function getSubScript() + public function setPosition($value = null) { - return $this->isSubScript(); + $this->position = $this->setIntVal($value, null); + + return $this; } /** - * Get strikethrough + * Set html css white-space value. It is expected that only pre-wrap and normal (default) are useful. * - * @deprecated 0.10.0 - * @codeCoverageIgnore + * @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 getStrikethrough() + public function getWhiteSpace(): string { - return $this->isStrikethrough(); + return $this->whiteSpace; } /** - * Get paragraph style + * Set generic font for fallback for html. * - * @deprecated 0.11.0 - * @codeCoverageIgnore + * @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 getParagraphStyle() + public function getFallbackFont(): string { - return $this->getParagraph(); + return $this->fallbackFont; } } diff --git a/src/PhpWord/Style/Frame.php b/src/PhpWord/Style/Frame.php index 5fef5f0182..016722f36c 100644 --- a/src/PhpWord/Style/Frame.php +++ b/src/PhpWord/Style/Frame.php @@ -1,4 +1,5 @@ alignment = new Alignment(); $this->setStyleByArray($style); } /** - * Get alignment + * @since 0.13.0 * * @return string */ - public function getAlign() + public function getAlignment() { - return $this->alignment->getValue(); + return $this->alignment; } /** - * Set alignment + * @since 0.13.0 * * @param string $value + * * @return self */ - public function setAlign($value = null) + public function setAlignment($value) { - $this->alignment->setValue($value); + if (Jc::isValid($value)) { + $this->alignment = $value; + } return $this; } /** - * Get unit + * Get unit. * * @return string */ @@ -216,9 +257,10 @@ public function getUnit() } /** - * Set unit + * Set unit. * * @param string $value + * * @return self */ public function setUnit($value) @@ -229,9 +271,9 @@ public function setUnit($value) } /** - * Get width + * Get width. * - * @return int|float + * @return float|int */ public function getWidth() { @@ -239,9 +281,10 @@ public function getWidth() } /** - * Set width + * Set width. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setWidth($value = null) @@ -252,9 +295,9 @@ public function setWidth($value = null) } /** - * Get height + * Get height. * - * @return int|float + * @return float|int */ public function getHeight() { @@ -262,9 +305,10 @@ public function getHeight() } /** - * Set height + * Set height. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setHeight($value = null) @@ -275,9 +319,9 @@ public function setHeight($value = null) } /** - * Get left + * Get left. * - * @return int|float + * @return float|int */ public function getLeft() { @@ -285,9 +329,10 @@ public function getLeft() } /** - * Set left + * Set left. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setLeft($value = 0) @@ -298,9 +343,9 @@ public function setLeft($value = 0) } /** - * Get topmost position + * Get topmost position. * - * @return int|float + * @return float|int */ public function getTop() { @@ -308,9 +353,10 @@ public function getTop() } /** - * Set topmost position + * Set topmost position. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setTop($value = 0) @@ -321,7 +367,7 @@ public function setTop($value = 0) } /** - * Get position type + * Get position type. * * @return string */ @@ -331,24 +377,25 @@ public function getPos() } /** - * Set position type + * Set position type. * * @param string $value + * * @return self */ public function setPos($value) { - $enum = array( + $enum = [ self::POS_ABSOLUTE, self::POS_RELATIVE, - ); + ]; $this->pos = $this->setEnumVal($value, $enum, $this->pos); return $this; } /** - * Get horizontal position + * Get horizontal position. * * @return string */ @@ -358,30 +405,31 @@ public function getHPos() } /** - * Set horizontal position + * Set horizontal position. * * @since 0.12.0 "absolute" option is available. * * @param string $value + * * @return self */ public function setHPos($value) { - $enum = array( + $enum = [ self::POS_ABSOLUTE, self::POS_LEFT, self::POS_CENTER, self::POS_RIGHT, self::POS_INSIDE, self::POS_OUTSIDE, - ); + ]; $this->hPos = $this->setEnumVal($value, $enum, $this->hPos); return $this; } /** - * Get vertical position + * Get vertical position. * * @return string */ @@ -391,30 +439,31 @@ public function getVPos() } /** - * Set vertical position + * Set vertical position. * * @since 0.12.0 "absolute" option is available. * * @param string $value + * * @return self */ public function setVPos($value) { - $enum = array( + $enum = [ self::POS_ABSOLUTE, self::POS_TOP, self::POS_CENTER, self::POS_BOTTOM, self::POS_INSIDE, self::POS_OUTSIDE, - ); + ]; $this->vPos = $this->setEnumVal($value, $enum, $this->vPos); return $this; } /** - * Get horizontal position relative to + * Get horizontal position relative to. * * @return string */ @@ -424,14 +473,15 @@ public function getHPosRelTo() } /** - * Set horizontal position relative to + * Set horizontal position relative to. * * @param string $value + * * @return self */ public function setHPosRelTo($value) { - $enum = array( + $enum = [ self::POS_RELTO_MARGIN, self::POS_RELTO_PAGE, self::POS_RELTO_COLUMN, @@ -440,14 +490,14 @@ public function setHPosRelTo($value) self::POS_RELTO_RMARGIN, self::POS_RELTO_IMARGIN, self::POS_RELTO_OMARGIN, - ); + ]; $this->hPosRelTo = $this->setEnumVal($value, $enum, $this->hPosRelTo); return $this; } /** - * Get vertical position relative to + * Get vertical position relative to. * * @return string */ @@ -457,14 +507,15 @@ public function getVPosRelTo() } /** - * Set vertical position relative to + * Set vertical position relative to. * * @param string $value + * * @return self */ public function setVPosRelTo($value) { - $enum = array( + $enum = [ self::POS_RELTO_MARGIN, self::POS_RELTO_PAGE, self::POS_RELTO_TEXT, @@ -473,14 +524,14 @@ public function setVPosRelTo($value) self::POS_RELTO_BMARGIN, self::POS_RELTO_IMARGIN, self::POS_RELTO_OMARGIN, - ); + ]; $this->vPosRelTo = $this->setEnumVal($value, $enum, $this->vPosRelTo); return $this; } /** - * Get wrap type + * Get wrap type. * * @return string */ @@ -490,14 +541,15 @@ public function getWrap() } /** - * Set wrap type + * Set wrap type. * * @param string $value + * * @return self */ public function setWrap($value) { - $enum = array( + $enum = [ self::WRAP_INLINE, self::WRAP_SQUARE, self::WRAP_TIGHT, @@ -505,9 +557,129 @@ public function setWrap($value) self::WRAP_TOPBOTTOM, self::WRAP_BEHIND, self::WRAP_INFRONT, - ); + ]; $this->wrap = $this->setEnumVal($value, $enum, $this->wrap); return $this; } + + /** + * Get top distance from text wrap. + * + * @return float + */ + public function getWrapDistanceTop() + { + return $this->wrapDistanceTop; + } + + /** + * Set top distance from text wrap. + * + * @param int $value + * + * @return self + */ + public function setWrapDistanceTop($value = null) + { + $this->wrapDistanceTop = $this->setFloatVal($value, null); + + return $this; + } + + /** + * Get bottom distance from text wrap. + * + * @return float + */ + public function getWrapDistanceBottom() + { + return $this->wrapDistanceBottom; + } + + /** + * Set bottom distance from text wrap. + * + * @param float $value + * + * @return self + */ + public function setWrapDistanceBottom($value = null) + { + $this->wrapDistanceBottom = $this->setFloatVal($value, null); + + return $this; + } + + /** + * Get left distance from text wrap. + * + * @return float + */ + public function getWrapDistanceLeft() + { + return $this->wrapDistanceLeft; + } + + /** + * Set left distance from text wrap. + * + * @param float $value + * + * @return self + */ + public function setWrapDistanceLeft($value = null) + { + $this->wrapDistanceLeft = $this->setFloatVal($value, null); + + return $this; + } + + /** + * Get right distance from text wrap. + * + * @return float + */ + public function getWrapDistanceRight() + { + return $this->wrapDistanceRight; + } + + /** + * Set right distance from text wrap. + * + * @param float $value + * + * @return self + */ + public function setWrapDistanceRight($value = null) + { + $this->wrapDistanceRight = $this->setFloatVal($value, null); + + return $this; + } + + /** + * Get position. + * + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * Set position. + * + * @param int $value + * + * @return self + */ + public function setPosition($value = null) + { + $this->position = $this->setIntVal($value, null); + + return $this; + } } diff --git a/src/PhpWord/Style/Image.php b/src/PhpWord/Style/Image.php index babfa6798e..f53a778793 100644 --- a/src/PhpWord/Style/Image.php +++ b/src/PhpWord/Style/Image.php @@ -1,4 +1,5 @@ setUnit('px'); + $this->setUnit(self::UNIT_PT); - // Backward compatilibity setting + // Backward compatibility setting // @todo Remove on 1.0.0 $this->setWrap(self::WRAPPING_STYLE_INLINE); $this->setHPos(self::POSITION_HORIZONTAL_LEFT); @@ -72,9 +74,9 @@ public function __construct() } /** - * Get margin top + * Get margin top. * - * @return int|float + * @return float|int */ public function getMarginTop() { @@ -82,10 +84,12 @@ public function getMarginTop() } /** - * Set margin top + * Set margin top. * * @ignoreScrutinizerPatch - * @param int|float $value + * + * @param float|int $value + * * @return self */ public function setMarginTop($value = 0) @@ -96,9 +100,9 @@ public function setMarginTop($value = 0) } /** - * Get margin left + * Get margin left. * - * @return int|float + * @return float|int */ public function getMarginLeft() { @@ -106,10 +110,12 @@ public function getMarginLeft() } /** - * Set margin left + * Set margin left. * * @ignoreScrutinizerPatch - * @param int|float $value + * + * @param float|int $value + * * @return self */ public function setMarginLeft($value = 0) @@ -120,7 +126,7 @@ public function setMarginLeft($value = 0) } /** - * Get wrapping style + * Get wrapping style. * * @return string */ @@ -130,10 +136,10 @@ public function getWrappingStyle() } /** - * Set wrapping style + * Set wrapping style. * * @param string $wrappingStyle - * @throws \InvalidArgumentException + * * @return self */ public function setWrappingStyle($wrappingStyle) @@ -144,7 +150,7 @@ public function setWrappingStyle($wrappingStyle) } /** - * Get positioning type + * Get positioning type. * * @return string */ @@ -154,10 +160,10 @@ public function getPositioning() } /** - * Set positioning type + * Set positioning type. * * @param string $positioning - * @throws \InvalidArgumentException + * * @return self */ public function setPositioning($positioning) @@ -168,7 +174,7 @@ public function setPositioning($positioning) } /** - * Get horizontal alignment + * Get horizontal alignment. * * @return string */ @@ -178,10 +184,10 @@ public function getPosHorizontal() } /** - * Set horizontal alignment + * Set horizontal alignment. * * @param string $alignment - * @throws \InvalidArgumentException + * * @return self */ public function setPosHorizontal($alignment) @@ -192,7 +198,7 @@ public function setPosHorizontal($alignment) } /** - * Get vertical alignment + * Get vertical alignment. * * @return string */ @@ -202,10 +208,10 @@ public function getPosVertical() } /** - * Set vertical alignment + * Set vertical alignment. * * @param string $alignment - * @throws \InvalidArgumentException + * * @return self */ public function setPosVertical($alignment) @@ -216,7 +222,7 @@ public function setPosVertical($alignment) } /** - * Get horizontal relation + * Get horizontal relation. * * @return string */ @@ -226,10 +232,10 @@ public function getPosHorizontalRel() } /** - * Set horizontal relation + * Set horizontal relation. * * @param string $relto - * @throws \InvalidArgumentException + * * @return self */ public function setPosHorizontalRel($relto) @@ -240,7 +246,7 @@ public function setPosHorizontalRel($relto) } /** - * Get vertical relation + * Get vertical relation. * * @return string */ @@ -250,10 +256,10 @@ public function getPosVerticalRel() } /** - * Set vertical relation + * Set vertical relation. * * @param string $relto - * @throws \InvalidArgumentException + * * @return self */ public function setPosVerticalRel($relto) diff --git a/src/PhpWord/Style/Indentation.php b/src/PhpWord/Style/Indentation.php index 5854204a9c..f2b646f7b6 100644 --- a/src/PhpWord/Style/Indentation.php +++ b/src/PhpWord/Style/Indentation.php @@ -1,4 +1,5 @@ setStyleByArray($style); } /** - * Get left - * - * @return int|float + * Get left. */ - public function getLeft() + public function getLeft(): ?float { return $this->left; } /** - * Set left - * - * @param int|float $value - * @return self + * Set left. */ - public function setLeft($value = null) + public function setLeft(?float $value): self { - $this->left = $this->setNumericVal($value, $this->left); + $this->left = $this->setNumericVal($value); return $this; } /** - * Get right - * - * @return int|float + * Get right. */ - public function getRight() + public function getRight(): ?float { return $this->right; } /** - * Set right - * - * @param int|float $value - * @return self + * Set right. */ - public function setRight($value = null) + public function setRight(?float $value): self { - $this->right = $this->setNumericVal($value, $this->right); + $this->right = $this->setNumericVal($value); return $this; } /** - * Get first line - * - * @return int|float + * Get first line. */ - public function getFirstLine() + public function getFirstLine(): ?float { return $this->firstLine; } /** - * Set first line - * - * @param int|float $value - * @return self + * Set first line. */ - public function setFirstLine($value = null) + public function setFirstLine(?float $value): self { - $this->firstLine = $this->setNumericVal($value, $this->firstLine); + $this->firstLine = $this->setNumericVal($value); return $this; } /** - * Get hanging - * - * @return int|float + * Get first line chars. */ - public function getHanging() + public function getFirstLineChars(): int + { + return $this->firstLineChars; + } + + /** + * Set first line chars. + */ + public function setFirstLineChars(int $value): self + { + $this->firstLineChars = $this->setIntVal($value, $this->firstLineChars); + + return $this; + } + + /** + * Get hanging. + */ + public function getHanging(): ?float { return $this->hanging; } /** - * Set hanging - * - * @param int|float $value - * @return self + * Set hanging. */ - public function setHanging($value = null) + public function setHanging(?float $value = null): self { - $this->hanging = $this->setNumericVal($value, $this->hanging); + $this->hanging = $this->setNumericVal($value); return $this; } diff --git a/src/PhpWord/Style/Language.php b/src/PhpWord/Style/Language.php new file mode 100644 index 0000000000..641ed7b41e --- /dev/null +++ b/src/PhpWord/Style/Language.php @@ -0,0 +1,261 @@ +setLatin($latin); + } + if (!empty($eastAsia)) { + $this->setEastAsia($eastAsia); + } + if (!empty($bidirectional)) { + $this->setBidirectional($bidirectional); + } + } + + /** + * Set the Latin Language. + * + * @param string $latin + * The value for the latin language + */ + public function setLatin(?string $latin): self + { + $this->latin = $this->validateLocale($latin); + + return $this; + } + + /** + * Get the Latin Language. + */ + public function getLatin(): ?string + { + return $this->latin; + } + + /** + * Set the Language ID. + * + * @param int $langId + * The value for the language ID + * + * @return self + * + * @see https://technet.microsoft.com/en-us/library/cc287874(v=office.12).aspx + */ + public function setLangId($langId) + { + $this->langId = $langId; + + return $this; + } + + /** + * Get the Language ID. + * + * @return int + */ + public function getLangId() + { + return $this->langId; + } + + /** + * Set the East Asian Language. + * + * @param string $eastAsia + * The value for the east asian language + * + * @return self + */ + public function setEastAsia($eastAsia) + { + $this->eastAsia = $this->validateLocale($eastAsia); + + return $this; + } + + /** + * Get the East Asian Language. + * + * @return null|string + */ + public function getEastAsia() + { + return $this->eastAsia; + } + + /** + * Set the Complex Script Language. + * + * @param string $bidirectional + * The value for the complex script language + * + * @return self + */ + public function setBidirectional($bidirectional) + { + $this->bidirectional = $this->validateLocale($bidirectional); + + return $this; + } + + /** + * Get the Complex Script Language. + * + * @return null|string + */ + public function getBidirectional() + { + return $this->bidirectional; + } + + /** + * Validates that the language passed is in the format xx-xx. + * + * @param string $locale + * + * @return string + */ + private function validateLocale($locale) + { + if ($locale !== null) { + $locale = str_replace('_', '-', $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'); + } + + return $locale; + } +} diff --git a/src/PhpWord/Style/Line.php b/src/PhpWord/Style/Line.php index 44f5422991..cc5a12ffe9 100644 --- a/src/PhpWord/Style/Line.php +++ b/src/PhpWord/Style/Line.php @@ -1,4 +1,5 @@ connectorType = $this->setEnumVal($value, $enum, $this->connectorType); return $this; } /** - * Get weight + * Get weight. * * @return int */ @@ -161,9 +165,10 @@ public function getWeight() } /** - * Set weight + * Set weight. * * @param int $value Weight in points + * * @return self */ public function setWeight($value = null) @@ -174,7 +179,7 @@ public function setWeight($value = null) } /** - * Get color + * Get color. * * @return string */ @@ -184,9 +189,10 @@ public function getColor() } /** - * Set color + * Set color. * * @param string $value + * * @return self */ public function setColor($value = null) @@ -197,7 +203,7 @@ public function setColor($value = null) } /** - * Get beginArrow + * Get beginArrow. * * @return string */ @@ -207,24 +213,25 @@ public function getBeginArrow() } /** - * Set beginArrow + * Set beginArrow. * * @param string $value + * * @return self */ public function setBeginArrow($value = null) { - $enum = array( + $enum = [ self::ARROW_STYLE_BLOCK, self::ARROW_STYLE_CLASSIC, self::ARROW_STYLE_DIAMOND, - self::ARROW_STYLE_OPEN, self::ARROW_STYLE_OVAL - ); + self::ARROW_STYLE_OPEN, self::ARROW_STYLE_OVAL, + ]; $this->beginArrow = $this->setEnumVal($value, $enum, $this->beginArrow); return $this; } /** - * Get endArrow + * Get endArrow. * * @return string */ @@ -234,24 +241,25 @@ public function getEndArrow() } /** - * Set endArrow + * Set endArrow. * * @param string $value + * * @return self */ public function setEndArrow($value = null) { - $enum = array( + $enum = [ self::ARROW_STYLE_BLOCK, self::ARROW_STYLE_CLASSIC, self::ARROW_STYLE_DIAMOND, - self::ARROW_STYLE_OPEN, self::ARROW_STYLE_OVAL - ); + self::ARROW_STYLE_OPEN, self::ARROW_STYLE_OVAL, + ]; $this->endArrow = $this->setEnumVal($value, $enum, $this->endArrow); return $this; } /** - * Get Dash + * Get Dash. * * @return string */ @@ -261,18 +269,19 @@ public function getDash() } /** - * Set Dash + * Set Dash. * * @param string $value + * * @return self */ public function setDash($value = null) { - $enum = array( + $enum = [ self::DASH_STYLE_DASH, self::DASH_STYLE_DASH_DOT, self::DASH_STYLE_LONG_DASH, self::DASH_STYLE_LONG_DASH_DOT, self::DASH_STYLE_LONG_DASH_DOT_DOT, self::DASH_STYLE_ROUND_DOT, - self::DASH_STYLE_SQUARE_DOT - ); + self::DASH_STYLE_SQUARE_DOT, + ]; $this->dash = $this->setEnumVal($value, $enum, $this->dash); return $this; diff --git a/src/PhpWord/Style/LineNumbering.php b/src/PhpWord/Style/LineNumbering.php index b93ce03f0f..95267a295e 100644 --- a/src/PhpWord/Style/LineNumbering.php +++ b/src/PhpWord/Style/LineNumbering.php @@ -1,4 +1,5 @@ setStyleByArray($style); } /** - * Get start + * Get start. * * @return int */ @@ -80,9 +82,10 @@ public function getStart() } /** - * Set start + * Set start. * * @param int $value + * * @return self */ public function setStart($value = null) @@ -93,7 +96,7 @@ public function setStart($value = null) } /** - * Get increment + * Get increment. * * @return int */ @@ -103,9 +106,10 @@ public function getIncrement() } /** - * Set increment + * Set increment. * * @param int $value + * * @return self */ public function setIncrement($value = null) @@ -116,9 +120,9 @@ public function setIncrement($value = null) } /** - * Get distance + * Get distance. * - * @return int|float + * @return float|int */ public function getDistance() { @@ -126,9 +130,10 @@ public function getDistance() } /** - * Set distance + * Set distance. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setDistance($value = null) @@ -139,7 +144,7 @@ public function setDistance($value = null) } /** - * Get restart + * Get restart. * * @return string */ @@ -149,14 +154,15 @@ public function getRestart() } /** - * Set distance + * Set distance. * * @param string $value + * * @return self */ public function setRestart($value = null) { - $enum = array(self::LINE_NUMBERING_CONTINUOUS, self::LINE_NUMBERING_NEW_PAGE, self::LINE_NUMBERING_NEW_SECTION); + $enum = [self::LINE_NUMBERING_CONTINUOUS, self::LINE_NUMBERING_NEW_PAGE, self::LINE_NUMBERING_NEW_SECTION]; $this->restart = $this->setEnumVal($value, $enum, $this->restart); return $this; diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index a689c691c0..e34aeb7c7c 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -1,4 +1,5 @@ listType = $this->setEnumVal($value, $enum, $this->listType); $this->getListTypeStyle(); @@ -102,7 +106,7 @@ public function setListType($value = self::TYPE_BULLET_FILLED) } /** - * Get numbering style name + * Get numbering style name. * * @return string */ @@ -112,9 +116,10 @@ public function getNumStyle() } /** - * Set numbering style name + * Set numbering style name. * * @param string $value + * * @return self */ public function setNumStyle($value) @@ -130,9 +135,9 @@ public function setNumStyle($value) } /** - * Get numbering Id + * Get numbering Id. * - * @return integer + * @return int */ public function getNumId() { @@ -140,28 +145,46 @@ public function getNumId() } /** - * Get legacy numbering definition + * Set numbering Id. Same numId means same list. + * + * @param mixed $numInt + */ + public function setNumId($numInt): void + { + $this->numId = $numInt; + $this->getListTypeStyle(); + } + + /** + * Get legacy numbering definition. * * @return array + * * @since 0.10.0 */ private function getListTypeStyle() { // Check if legacy style already registered in global Style collection - $numStyle = "PHPWordList{$this->listType}"; + $numStyle = 'PHPWordListType' . $this->listType; + + if ($this->numId) { + $numStyle .= 'NumId' . $this->numId; + } + if (Style::getStyle($numStyle) !== null) { $this->setNumStyle($numStyle); + return; } // Property mapping for numbering level information - $properties = array('start', 'format', 'text', 'align', 'tabPos', 'left', 'hanging', 'font', 'hint'); + $properties = ['start', 'format', 'text', 'alignment', 'tabPos', 'left', 'hanging', 'font', 'hint']; // Legacy level information - $listTypeStyles = array( - self::TYPE_SQUARE_FILLED => array( + $listTypeStyles = [ + self::TYPE_SQUARE_FILLED => [ 'type' => 'hybridMultilevel', - 'levels' => array( + 'levels' => [ 0 => '1, bullet, , left, 720, 720, 360, Wingdings, default', 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', @@ -171,11 +194,11 @@ private function getListTypeStyle() 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', - ), - ), - self::TYPE_BULLET_FILLED => array( + ], + ], + self::TYPE_BULLET_FILLED => [ 'type' => 'hybridMultilevel', - 'levels' => array( + 'levels' => [ 0 => '1, bullet, , left, 720, 720, 360, Symbol, default', 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', @@ -185,11 +208,11 @@ private function getListTypeStyle() 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', - ), - ), - self::TYPE_BULLET_EMPTY => array( + ], + ], + self::TYPE_BULLET_EMPTY => [ 'type' => 'hybridMultilevel', - 'levels' => array( + 'levels' => [ 0 => '1, bullet, o, left, 720, 720, 360, Courier New, default', 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', @@ -199,11 +222,11 @@ private function getListTypeStyle() 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', - ), - ), - self::TYPE_NUMBER => array( + ], + ], + self::TYPE_NUMBER => [ 'type' => 'hybridMultilevel', - 'levels' => array( + 'levels' => [ 0 => '1, decimal, %1., left, 720, 720, 360, , default', 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', @@ -213,11 +236,11 @@ private function getListTypeStyle() 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', - ), - ), - self::TYPE_NUMBER_NESTED => array( + ], + ], + self::TYPE_NUMBER_NESTED => [ 'type' => 'multilevel', - 'levels' => array( + 'levels' => [ 0 => '1, decimal, %1., left, 360, 360, 360, , ', 1 => '1, decimal, %1.%2., left, 792, 792, 432, , ', 2 => '1, decimal, %1.%2.%3., left, 1224, 1224, 504, , ', @@ -227,11 +250,11 @@ private function getListTypeStyle() 6 => '1, decimal, %1.%2.%3.%4.%5.%6.%7., left, 3600, 3240, 1080, , ', 7 => '1, decimal, %1.%2.%3.%4.%5.%6.%7.%8., left, 3960, 3744, 1224, , ', 8 => '1, decimal, %1.%2.%3.%4.%5.%6.%7.%8.%9., left, 4680, 4320, 1440, , ', - ), - ), - self::TYPE_ALPHANUM => array( + ], + ], + self::TYPE_ALPHANUM => [ 'type' => 'multilevel', - 'levels' => array( + 'levels' => [ 0 => '1, decimal, %1., left, 720, 720, 360, , ', 1 => '1, lowerLetter, %2., left, 1440, 1440, 360, , ', 2 => '1, lowerRoman, %3., right, 2160, 2160, 180, , ', @@ -241,17 +264,18 @@ private function getListTypeStyle() 6 => '1, decimal, %7., left, 5040, 5040, 360, , ', 7 => '1, lowerLetter, %8., left, 5760, 5760, 360, , ', 8 => '1, lowerRoman, %9., right, 6480, 6480, 180, , ', - ), - ), - ); + ], + ], + ]; // Populate style and register to global Style register $style = $listTypeStyles[$this->listType]; + $numProperties = count($properties); foreach ($style['levels'] as $key => $value) { - $level = array(); + $level = []; $levelProperties = explode(', ', $value); $level['level'] = $key; - for ($i = 0; $i < count($properties); $i++) { + for ($i = 0; $i < $numProperties; ++$i) { $property = $properties[$i]; $level[$property] = $levelProperties[$i]; } diff --git a/src/PhpWord/Style/Numbering.php b/src/PhpWord/Style/Numbering.php index 726af2bec7..2b34f2a9bb 100644 --- a/src/PhpWord/Style/Numbering.php +++ b/src/PhpWord/Style/Numbering.php @@ -1,4 +1,5 @@ numId; } /** - * Set Id - * - * @param integer $value - * @return self + * Set Id. */ - public function setNumId($value) + public function setNumId(int $value): self { $this->numId = $this->setIntVal($value, $this->numId); @@ -74,56 +72,46 @@ public function setNumId($value) } /** - * Get multilevel type - * - * @return string + * Get multilevel type. */ - public function getType() + public function getType(): ?string { return $this->type; } /** - * Set multilevel type - * - * @param string $value - * @return self + * Set multilevel type. */ - public function setType($value) + public function setType(string $value): self { - $enum = array('singleLevel', 'multilevel', 'hybridMultilevel'); + $enum = ['singleLevel', 'multilevel', 'hybridMultilevel']; $this->type = $this->setEnumVal($value, $enum, $this->type); return $this; } /** - * Get levels + * Get levels. * * @return NumberingLevel[] */ - public function getLevels() + public function getLevels(): array { return $this->levels; } /** - * Set multilevel type - * - * @param array $values - * @return self + * Set multilevel type. */ - public function setLevels($values) + public function setLevels(array $values): self { - if (is_array($values)) { - foreach ($values as $key => $value) { - $numberingLevel = new NumberingLevel(); - if (is_array($value)) { - $numberingLevel->setStyleByArray($value); - $numberingLevel->setLevel($key); - } - $this->levels[$key] = $numberingLevel; + foreach ($values as $key => $value) { + $numberingLevel = new NumberingLevel(); + if (is_array($value)) { + $numberingLevel->setStyleByArray($value); + $numberingLevel->setLevel($key); } + $this->levels[$key] = $numberingLevel; } return $this; diff --git a/src/PhpWord/Style/NumberingLevel.php b/src/PhpWord/Style/NumberingLevel.php index 32e61c8962..f2505a330d 100644 --- a/src/PhpWord/Style/NumberingLevel.php +++ b/src/PhpWord/Style/NumberingLevel.php @@ -1,4 +1,5 @@ level = $this->setIntVal($value, $this->level); + return $this; } /** - * Get start + * Get start. * - * @return integer + * @return int */ public function getStart() { @@ -157,19 +169,21 @@ public function getStart() } /** - * Set start + * Set start. + * + * @param int $value * - * @param integer $value * @return self */ public function setStart($value) { $this->start = $this->setIntVal($value, $this->start); + return $this; } /** - * Get format + * Get format. * * @return string */ @@ -179,22 +193,23 @@ public function getFormat() } /** - * Set format + * Set format. * * @param string $value + * * @return self */ public function setFormat($value) { - $enum = array('bullet', 'decimal', 'upperRoman', 'lowerRoman', 'upperLetter', 'lowerLetter'); - $this->format = $this->setEnumVal($value, $enum, $this->format); + $this->format = $this->setEnumVal($value, NumberFormat::values(), $this->format); + return $this; } /** - * Get start + * Get restart. * - * @return integer + * @return int */ public function getRestart() { @@ -202,19 +217,21 @@ public function getRestart() } /** - * Set start + * Set restart. + * + * @param int $value * - * @param integer $value * @return self */ public function setRestart($value) { $this->restart = $this->setIntVal($value, $this->restart); + return $this; } /** - * Get related paragraph style + * Get related paragraph style. * * @return string */ @@ -224,19 +241,21 @@ public function getPStyle() } /** - * Set related paragraph style + * Set related paragraph style. * * @param string $value + * * @return self */ public function setPStyle($value) { $this->pStyle = $value; + return $this; } /** - * Get suffix + * Get suffix. * * @return string */ @@ -246,20 +265,22 @@ public function getSuffix() } /** - * Set suffix + * Set suffix. * * @param string $value + * * @return self */ public function setSuffix($value) { - $enum = array('tab', 'space', 'nothing'); + $enum = ['tab', 'space', 'nothing']; $this->suffix = $this->setEnumVal($value, $enum, $this->suffix); + return $this; } /** - * Get text + * Get text. * * @return string */ @@ -269,44 +290,49 @@ public function getText() } /** - * Set text + * Set text. * * @param string $value + * * @return self */ public function setText($value) { $this->text = $value; + return $this; } /** - * Get align + * @since 0.13.0 * * @return string */ - public function getAlign() + public function getAlignment() { - return $this->align; + return $this->alignment; } /** - * Set align + * @since 0.13.0 * * @param string $value + * * @return self */ - public function setAlign($value) + public function setAlignment($value) { - $enum = array('left', 'center', 'right', 'both'); - $this->align = $this->setEnumVal($value, $enum, $this->align); + if (Jc::isValid($value)) { + $this->alignment = $value; + } + return $this; } /** - * Get left + * Get left. * - * @return integer + * @return int */ public function getLeft() { @@ -314,21 +340,23 @@ public function getLeft() } /** - * Set left + * Set left. + * + * @param int $value * - * @param integer $value * @return self */ public function setLeft($value) { $this->left = $this->setIntVal($value, $this->left); + return $this; } /** - * Get hanging + * Get hanging. * - * @return integer + * @return int */ public function getHanging() { @@ -336,21 +364,23 @@ public function getHanging() } /** - * Set hanging + * Set hanging. + * + * @param int $value * - * @param integer $value * @return self */ public function setHanging($value) { $this->hanging = $this->setIntVal($value, $this->hanging); + return $this; } /** - * Get tab + * Get tab. * - * @return integer + * @return int */ public function getTabPos() { @@ -358,19 +388,21 @@ public function getTabPos() } /** - * Set tab + * Set tab. + * + * @param int $value * - * @param integer $value * @return self */ public function setTabPos($value) { $this->tabPos = $this->setIntVal($value, $this->tabPos); + return $this; } /** - * Get font + * Get font. * * @return string */ @@ -380,19 +412,21 @@ public function getFont() } /** - * Set font + * Set font. * * @param string $value + * * @return self */ public function setFont($value) { $this->font = $value; + return $this; } /** - * Get hint + * Get hint. * * @return string */ @@ -402,14 +436,15 @@ public function getHint() } /** - * Set hint + * Set hint. * * @param string $value + * * @return self */ public function setHint($value = null) { - $enum = array('default', 'eastAsia', 'cs'); + $enum = ['default', 'eastAsia', 'cs']; $this->hint = $this->setEnumVal($value, $enum, $this->hint); return $this; diff --git a/src/PhpWord/Style/Outline.php b/src/PhpWord/Style/Outline.php index bfd14a1457..7706580825 100644 --- a/src/PhpWord/Style/Outline.php +++ b/src/PhpWord/Style/Outline.php @@ -1,4 +1,5 @@ setStyleByArray($style); } /** - * Get unit + * Get unit. * * @return string */ @@ -139,9 +144,9 @@ public function getUnit() } /** - * Get weight + * Get weight. * - * @return int|float + * @return float|int */ public function getWeight() { @@ -149,9 +154,10 @@ public function getWeight() } /** - * Set weight + * Set weight. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setWeight($value = null) @@ -162,7 +168,7 @@ public function setWeight($value = null) } /** - * Get color + * Get color. * * @return string */ @@ -172,9 +178,10 @@ public function getColor() } /** - * Set color + * Set color. * * @param string $value + * * @return self */ public function setColor($value = null) @@ -185,7 +192,7 @@ public function setColor($value = null) } /** - * Get dash type + * Get dash type. * * @return string */ @@ -195,9 +202,10 @@ public function getDash() } /** - * Set dash type + * Set dash type. * * @param string $value + * * @return self */ public function setDash($value = null) @@ -208,7 +216,7 @@ public function setDash($value = null) } /** - * Get line style + * Get line style. * * @return string */ @@ -218,22 +226,23 @@ public function getLine() } /** - * Set line style + * Set line style. * * @param string $value + * * @return self */ public function setLine($value = null) { - $enum = array(self::LINE_SINGLE, self::LINE_THIN_THIN, self::LINE_THIN_THICK, - self::LINE_THICK_THIN, self::LINE_THICK_BETWEEN_THIN); + $enum = [self::LINE_SINGLE, self::LINE_THIN_THIN, self::LINE_THIN_THICK, + self::LINE_THICK_THIN, self::LINE_THICK_BETWEEN_THIN, ]; $this->line = $this->setEnumVal($value, $enum, null); return $this; } /** - * Get endCap style + * Get endCap style. * * @return string */ @@ -243,21 +252,22 @@ public function getEndCap() } /** - * Set endCap style + * Set endCap style. * * @param string $value + * * @return self */ public function setEndCap($value = null) { - $enum = array(self::ENDCAP_FLAT, self::ENDCAP_SQUARE, self::ENDCAP_ROUND); + $enum = [self::ENDCAP_FLAT, self::ENDCAP_SQUARE, self::ENDCAP_ROUND]; $this->endCap = $this->setEnumVal($value, $enum, null); return $this; } /** - * Get startArrow + * Get startArrow. * * @return string */ @@ -267,22 +277,23 @@ public function getStartArrow() } /** - * Set pattern + * Set pattern. * * @param string $value + * * @return self */ public function setStartArrow($value = null) { - $enum = array(self::ARROW_NONE, self::ARROW_BLOCK, self::ARROW_CLASSIC, - self::ARROW_OVAL, self::ARROW_DIAMOND, self::ARROW_OPEN); + $enum = [self::ARROW_NONE, self::ARROW_BLOCK, self::ARROW_CLASSIC, + self::ARROW_OVAL, self::ARROW_DIAMOND, self::ARROW_OPEN, ]; $this->startArrow = $this->setEnumVal($value, $enum, null); return $this; } /** - * Get endArrow + * Get endArrow. * * @return string */ @@ -292,15 +303,16 @@ public function getEndArrow() } /** - * Set pattern + * Set pattern. * * @param string $value + * * @return self */ public function setEndArrow($value = null) { - $enum = array(self::ARROW_NONE, self::ARROW_BLOCK, self::ARROW_CLASSIC, - self::ARROW_OVAL, self::ARROW_DIAMOND, self::ARROW_OPEN); + $enum = [self::ARROW_NONE, self::ARROW_BLOCK, self::ARROW_CLASSIC, + self::ARROW_OVAL, self::ARROW_DIAMOND, self::ARROW_OPEN, ]; $this->endArrow = $this->setEnumVal($value, $enum, null); return $this; diff --git a/src/PhpWord/Style/Paper.php b/src/PhpWord/Style/Paper.php index 642666b5f1..c59ea42d7b 100644 --- a/src/PhpWord/Style/Paper.php +++ b/src/PhpWord/Style/Paper.php @@ -1,4 +1,5 @@ array(297, 420, 'mm'), - 'A4' => array(210, 297, 'mm'), - 'A5' => array(148, 210, 'mm'), - 'Folio' => array(8.5, 13, 'in'), - 'Legal' => array(8.5, 14, 'in'), - 'Letter' => array(8.5, 11, 'in'), - ); + private $sizes = [ + 'A3' => [297, 420, 'mm'], + 'A4' => [210, 297, 'mm'], + 'A5' => [148, 210, 'mm'], + 'B5' => [176, 250, 'mm'], + 'Folio' => [8.5, 13, 'in'], + 'Legal' => [8.5, 14, 'in'], + 'Letter' => [8.5, 11, 'in'], + ]; /** - * Paper size + * Paper size. * * @var string */ private $size = 'A4'; /** - * Width + * Width. * - * @var int (twip) + * @var float (twip) */ private $width; /** - * Height + * Height. * - * @var int (twip) + * @var float (twip) */ private $height; /** - * Create a new instance + * Create a new instance. * * @param string $size */ @@ -137,7 +141,7 @@ public function __construct($size = 'A4') } /** - * Get size + * Get size. * * @return string */ @@ -147,29 +151,33 @@ public function getSize() } /** - * Set size + * Set size. * * @param string $size + * * @return self */ public function setSize($size) { $this->size = $this->setEnumVal($size, array_keys($this->sizes), $this->size); - list($width, $height, $unit) = $this->sizes[$this->size]; - $multipliers = array('mm' => 56.5217, 'in' => 1440); - $multiplier = $multipliers[$unit]; + [$width, $height, $unit] = $this->sizes[$this->size]; - $this->width = (int)round($width * $multiplier); - $this->height = (int)round($height * $multiplier); + if ($unit == 'mm') { + $this->width = Converter::cmToTwip($width / 10); + $this->height = Converter::cmToTwip($height / 10); + } else { + $this->width = Converter::inchToTwip($width); + $this->height = Converter::inchToTwip($height); + } return $this; } /** - * Get width + * Get width. * - * @return int + * @return float */ public function getWidth() { @@ -177,9 +185,9 @@ public function getWidth() } /** - * Get height + * Get height. * - * @return int + * @return float */ public function getHeight() { diff --git a/src/PhpWord/Style/Paragraph.php b/src/PhpWord/Style/Paragraph.php index 964a4ec960..5ab7ade673 100644 --- a/src/PhpWord/Style/Paragraph.php +++ b/src/PhpWord/Style/Paragraph.php @@ -1,4 +1,5 @@ 'lineHeight'); + protected $aliases = ['line-height' => 'lineHeight', 'line-spacing' => 'spacing']; /** - * Parent style + * Parent style. * * @var string */ private $basedOn = 'Normal'; /** - * Style for next paragraph + * Style for next paragraph. * * @var string */ private $next; /** - * Alignment - * - * @var \PhpOffice\PhpWord\Style\Alignment + * @var string */ - private $alignment; + private $alignment = ''; /** - * Indentation + * Indentation. * - * @var \PhpOffice\PhpWord\Style\Indentation + * @var null|Indentation */ private $indentation; /** - * Spacing + * Spacing. * - * @var \PhpOffice\PhpWord\Style\Spacing + * @var Spacing */ private $spacing; /** - * Text line height + * Text line height. * - * @var int + * @var null|float|int */ private $lineHeight; /** - * Allow first/last line to display on a separate page + * Allow first/last line to display on a separate page. * * @var bool */ private $widowControl = true; /** - * Keep paragraph with next paragraph + * Keep paragraph with next paragraph. * * @var bool */ private $keepNext = false; /** - * Keep all lines on one page + * Keep all lines on one page. * * @var bool */ private $keepLines = false; /** - * Start paragraph on next page + * Start paragraph on next page. * * @var bool */ private $pageBreakBefore = false; /** - * Numbering style name + * Numbering style name. * * @var string */ private $numStyle; /** - * Numbering level + * Numbering level. * * @var int */ private $numLevel = 0; /** - * Set of Custom Tab Stops + * Set of Custom Tab Stops. * - * @var \PhpOffice\PhpWord\Style\Tab[] + * @var Tab[] */ - private $tabs = array(); + private $tabs = []; /** - * Shading + * Shading. * - * @var \PhpOffice\PhpWord\Style\Shading + * @var Shading */ private $shading; /** - * Create new instance + * Ignore Spacing Above and Below When Using Identical Styles. + * + * @var bool */ - public function __construct() - { - $this->alignment = new Alignment(); - } + private $contextualSpacing = false; + + /** + * Right to Left Paragraph Layout. + * + * @var ?bool + */ + private $bidi; /** - * Set Style value + * Vertical Character Alignment on Line. + * + * @var string + */ + private $textAlignment; + + /** + * Suppress hyphenation for paragraph. + * + * @var bool + */ + private $suppressAutoHyphens = false; + + /** + * Set Style value. * * @param string $key * @param mixed $value + * * @return self */ public function setStyleValue($key, $value) { - $key = String::removeUnderscorePrefix($key); - if ($key == 'indent' || $key == 'hanging') { - $value = $value * 720; - } elseif ($key == 'spacing') { - $value += 240; // because line height of 1 matches 240 twips + $key = Text::removeUnderscorePrefix($key); + if ('indent' == $key || 'hanging' == $key) { + $value = $value * 720; // 720 twips is 0.5 inch } return parent::setStyleValue($key, $value); } /** - * Get style values + * Get style values. * * An experiment to retrieve all style values in one function. This will * reduce function call and increase cohesion between functions. Should be * implemented in all styles. * * @ignoreScrutinizerPatch + * * @return array */ public function getStyleValues() { - $styles = array( - 'name' => $this->getStyleName(), - 'basedOn' => $this->getBasedOn(), - 'next' => $this->getNext(), - 'alignment' => $this->getAlign(), - 'indentation' => $this->getIndentation(), - 'spacing' => $this->getSpace(), - 'pagination' => array( - 'widowControl' => $this->hasWidowControl(), - 'keepNext' => $this->isKeepNext(), - 'keepLines' => $this->isKeepLines(), - 'pageBreak' => $this->hasPageBreakBefore(), - ), - 'numbering' => array( - 'style' => $this->getNumStyle(), - 'level' => $this->getNumLevel(), - ), - 'tabs' => $this->getTabs(), - 'shading' => $this->getShading(), - ); + $styles = [ + 'name' => $this->getStyleName(), + 'basedOn' => $this->getBasedOn(), + 'next' => $this->getNext(), + 'alignment' => $this->getAlignment(), + 'indentation' => $this->getIndentation(), + 'spacing' => $this->getSpace(), + 'pagination' => [ + 'widowControl' => $this->hasWidowControl(), + 'keepNext' => $this->isKeepNext(), + 'keepLines' => $this->isKeepLines(), + 'pageBreak' => $this->hasPageBreakBefore(), + ], + 'numbering' => [ + 'style' => $this->getNumStyle(), + 'level' => $this->getNumLevel(), + ], + 'tabs' => $this->getTabs(), + 'shading' => $this->getShading(), + 'contextualSpacing' => $this->hasContextualSpacing(), + 'bidi' => $this->isBidi(), + 'textAlignment' => $this->getTextAlignment(), + 'suppressAutoHyphens' => $this->hasSuppressAutoHyphens(), + ]; return $styles; } /** - * Get alignment + * @since 0.13.0 * * @return string */ - public function getAlign() + public function getAlignment() { - return $this->alignment->getValue(); + return $this->alignment; } /** - * Set alignment + * @since 0.13.0 * * @param string $value + * * @return self */ - public function setAlign($value = null) + public function setAlignment($value) { - $this->alignment->setValue($value); + if (Jc::isValid($value)) { + $this->alignment = $value; + } return $this; } /** - * Get parent style ID + * Get parent style ID. * * @return string */ @@ -256,9 +285,10 @@ public function getBasedOn() } /** - * Set parent style ID + * Set parent style ID. * * @param string $value + * * @return self */ public function setBasedOn($value = 'Normal') @@ -269,7 +299,7 @@ public function setBasedOn($value = 'Normal') } /** - * Get style for next paragraph + * Get style for next paragraph. * * @return string */ @@ -279,9 +309,10 @@ public function getNext() } /** - * Set style for next paragraph + * Set style for next paragraph. * * @param string $value + * * @return self */ public function setNext($value = null) @@ -292,74 +323,144 @@ public function setNext($value = null) } /** - * Get shading - * - * @return \PhpOffice\PhpWord\Style\Indentation + * Get hanging. */ - public function getIndentation() + public function getHanging(): ?float { - return $this->indentation; + return $this->getChildStyleValue($this->indentation, 'hanging'); } /** - * Set shading + * Get indentation. * - * @param mixed $value - * @return self + * @deprecated 1.4.0 Use getIndentLeft */ - public function setIndentation($value = null) + public function getIndent(): ?float { - $this->setObjectVal($value, 'Indentation', $this->indentation); + return $this->getChildStyleValue($this->indentation, 'left'); + } - return $this; + /** + * Get indentation. + */ + public function getIndentation(): ?Indentation + { + return $this->indentation; } /** - * Get indentation - * - * @return int + * Get firstLine. + */ + public function getIndentFirstLine(): ?float + { + return $this->getChildStyleValue($this->indentation, 'firstLine'); + } + + /** + * Get left indentation. */ - public function getIndent() + public function getIndentLeft(): ?float { return $this->getChildStyleValue($this->indentation, 'left'); } /** - * Set indentation + * Get right indentation. + */ + public function getIndentRight(): ?float + { + return $this->getChildStyleValue($this->indentation, 'right'); + } + + /** + * Set hanging. * - * @param int $value - * @return self + * @deprecated 1.4.0 Use setIndentHanging */ - public function setIndent($value = null) + public function setHanging(?float $value = null): self { - return $this->setIndentation(array('left' => $value)); + return $this->setIndentation(['hanging' => $value]); } /** - * Get hanging + * Set indentation. * - * @return int + * @deprecated 1.4.0 Use setIndentLeft */ - public function getHanging() + public function setIndent(?float $value = null): self { - return $this->getChildStyleValue($this->indentation, 'hanging'); + return $this->setIndentation(['left' => $value]); } /** - * Set hanging + * Set indentation. * - * @param int $value - * @return self + * @param array{ + * left?:null|float|int|numeric-string, + * right?:null|float|int|numeric-string, + * hanging?:null|float|int|numeric-string, + * firstLine?:null|float|int|numeric-string + * } $value + */ + public function setIndentation(array $value = []): self + { + $value = array_map(function ($indent) { + if (is_string($indent) || is_numeric($indent)) { + $indent = $this->setFloatVal($indent); + } + + return $indent; + }, $value); + $this->setObjectVal($value, 'Indentation', $this->indentation); + + return $this; + } + + /** + * Set hanging indentation. + */ + public function setIndentHanging(?float $value = null): self + { + return $this->setIndentation(['hanging' => $value]); + } + + /** + * Set firstline indentation. + */ + public function setIndentFirstLine(?float $value = null): self + { + return $this->setIndentation(['firstLine' => $value]); + } + + /** + * Set firstlineChars indentation. + */ + public function setIndentFirstLineChars(int $value = 0): self + { + return $this->setIndentation(['firstLineChars' => $value]); + } + + /** + * Set left indentation. + */ + public function setIndentLeft(?float $value = null): self + { + return $this->setIndentation(['left' => $value]); + } + + /** + * Set right indentation. */ - public function setHanging($value = null) + public function setIndentRight(?float $value = null): self { - return $this->setIndentation(array('hanging' => $value)); + return $this->setIndentation(['right' => $value]); } /** - * Get spacing + * Get spacing. + * + * @return Spacing * - * @return \PhpOffice\PhpWord\Style\Spacing * @todo Rename to getSpacing in 1.0 */ public function getSpace() @@ -368,10 +469,12 @@ public function getSpace() } /** - * Set spacing + * Set spacing. * * @param mixed $value + * * @return self + * * @todo Rename to setSpacing in 1.0 */ public function setSpace($value = null) @@ -382,9 +485,9 @@ public function setSpace($value = null) } /** - * Get space before paragraph + * Get space before paragraph. * - * @return integer + * @return null|float|int */ public function getSpaceBefore() { @@ -392,20 +495,21 @@ public function getSpaceBefore() } /** - * Set space before paragraph + * Set space before paragraph. + * + * @param null|float|int $value * - * @param int $value * @return self */ public function setSpaceBefore($value = null) { - return $this->setSpace(array('before' => $value)); + return $this->setSpace(['before' => $value]); } /** - * Get space after paragraph + * Get space after paragraph. * - * @return integer + * @return null|float|int */ public function getSpaceAfter() { @@ -413,20 +517,21 @@ public function getSpaceAfter() } /** - * Set space after paragraph + * Set space after paragraph. + * + * @param null|float|int $value * - * @param int $value * @return self */ public function setSpaceAfter($value = null) { - return $this->setSpace(array('after' => $value)); + return $this->setSpace(['after' => $value]); } /** - * Get spacing between lines + * Get spacing between lines. * - * @return int + * @return null|float|int */ public function getSpacing() { @@ -434,20 +539,43 @@ public function getSpacing() } /** - * Set spacing between lines + * Set spacing between lines. + * + * @param null|float|int $value * - * @param int $value * @return self */ public function setSpacing($value = null) { - return $this->setSpace(array('line' => $value)); + return $this->setSpace(['line' => $value]); } /** - * Get line height + * Get spacing line rule. + * + * @return string + */ + public function getSpacingLineRule() + { + return $this->getChildStyleValue($this->spacing, 'lineRule'); + } + + /** + * Set the spacing line rule. + * + * @param string $value Possible values are defined in LineSpacingRule * - * @return int|float + * @return Paragraph + */ + public function setSpacingLineRule($value) + { + return $this->setSpace(['lineRule' => $value]); + } + + /** + * Get line height. + * + * @return null|float|int */ public function getLineHeight() { @@ -455,29 +583,31 @@ public function getLineHeight() } /** - * Set the line height + * Set the line height. + * + * @param float|int|string $lineHeight * - * @param int|float|string $lineHeight * @return self - * @throws \PhpOffice\PhpWord\Exception\InvalidStyleException */ public function setLineHeight($lineHeight) { if (is_string($lineHeight)) { - $lineHeight = floatval(preg_replace('/[^0-9\.\,]/', '', $lineHeight)); + $lineHeight = (float) (preg_replace('/[^0-9\.\,]/', '', $lineHeight)); } - if ((!is_integer($lineHeight) && !is_float($lineHeight)) || !$lineHeight) { + if ((!is_int($lineHeight) && !is_float($lineHeight)) || !$lineHeight) { throw new InvalidStyleException('Line height must be a valid number'); } $this->lineHeight = $lineHeight; - $this->setSpacing($lineHeight * self::LINE_HEIGHT); + $this->setSpacing(($lineHeight - 1) * self::LINE_HEIGHT); + $this->setSpacingLineRule(\PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO); + return $this; } /** - * Get allow first/last line to display on a separate page setting + * Get allow first/last line to display on a separate page setting. * * @return bool */ @@ -487,9 +617,10 @@ public function hasWidowControl() } /** - * Set keep paragraph with next paragraph setting + * Set keep paragraph with next paragraph setting. * * @param bool $value + * * @return self */ public function setWidowControl($value = true) @@ -500,7 +631,7 @@ public function setWidowControl($value = true) } /** - * Get keep paragraph with next paragraph setting + * Get keep paragraph with next paragraph setting. * * @return bool */ @@ -510,9 +641,10 @@ public function isKeepNext() } /** - * Set keep paragraph with next paragraph setting + * Set keep paragraph with next paragraph setting. * * @param bool $value + * * @return self */ public function setKeepNext($value = true) @@ -523,7 +655,7 @@ public function setKeepNext($value = true) } /** - * Get keep all lines on one page setting + * Get keep all lines on one page setting. * * @return bool */ @@ -533,9 +665,10 @@ public function isKeepLines() } /** - * Set keep all lines on one page setting + * Set keep all lines on one page setting. * * @param bool $value + * * @return self */ public function setKeepLines($value = true) @@ -546,7 +679,7 @@ public function setKeepLines($value = true) } /** - * Get start paragraph on next page setting + * Get start paragraph on next page setting. * * @return bool */ @@ -556,9 +689,10 @@ public function hasPageBreakBefore() } /** - * Set start paragraph on next page setting + * Set start paragraph on next page setting. * * @param bool $value + * * @return self */ public function setPageBreakBefore($value = true) @@ -569,7 +703,7 @@ public function setPageBreakBefore($value = true) } /** - * Get numbering style name + * Get numbering style name. * * @return string */ @@ -579,9 +713,10 @@ public function getNumStyle() } /** - * Set numbering style name + * Set numbering style name. * * @param string $value + * * @return self */ public function setNumStyle($value) @@ -592,7 +727,7 @@ public function setNumStyle($value) } /** - * Get numbering level + * Get numbering level. * * @return int */ @@ -602,9 +737,10 @@ public function getNumLevel() } /** - * Set numbering level + * Set numbering level. * * @param int $value + * * @return self */ public function setNumLevel($value = 0) @@ -615,9 +751,9 @@ public function setNumLevel($value = 0) } /** - * Get tabs + * Get tabs. * - * @return \PhpOffice\PhpWord\Style\Tab[] + * @return Tab[] */ public function getTabs() { @@ -625,9 +761,10 @@ public function getTabs() } /** - * Set tabs + * Set tabs. * * @param array $value + * * @return self */ public function setTabs($value = null) @@ -640,69 +777,116 @@ public function setTabs($value = null) } /** - * Get allow first/last line to display on a separate page setting + * Get shading. * - * @deprecated 0.10.0 - * @codeCoverageIgnore + * @return Shading */ - public function getWidowControl() + public function getShading() { - return $this->hasWidowControl(); + return $this->shading; } /** - * Get keep paragraph with next paragraph setting + * Set shading. + * + * @param mixed $value * - * @deprecated 0.10.0 - * @codeCoverageIgnore + * @return self */ - public function getKeepNext() + public function setShading($value = null) { - return $this->isKeepNext(); + $this->setObjectVal($value, 'Shading', $this->shading); + + return $this; } /** - * Get keep all lines on one page setting + * Get contextualSpacing. * - * @deprecated 0.10.0 - * @codeCoverageIgnore + * @return bool */ - public function getKeepLines() + public function hasContextualSpacing() { - return $this->isKeepLines(); + return $this->contextualSpacing; } /** - * Get start paragraph on next page setting + * Set contextualSpacing. + * + * @param bool $contextualSpacing * - * @deprecated 0.10.0 - * @codeCoverageIgnore + * @return self */ - public function getPageBreakBefore() + public function setContextualSpacing($contextualSpacing) { - return $this->hasPageBreakBefore(); + $this->contextualSpacing = $contextualSpacing; + + return $this; } /** - * Get shading + * Get bidirectional. * - * @return \PhpOffice\PhpWord\Style\Shading + * @return ?bool */ - public function getShading() + public function isBidi() { - return $this->shading; + return $this->bidi ?? Settings::isDefaultRtl(); } /** - * Set shading + * Set bidi. + * + * @param ?bool $bidi + * Set to true to write from right to left * - * @param mixed $value * @return self */ - public function setShading($value = null) + public function setBidi($bidi) { - $this->setObjectVal($value, 'Shading', $this->shading); + $this->bidi = $bidi; + + return $this; + } + + /** + * Get textAlignment. + * + * @return string + */ + public function getTextAlignment() + { + return $this->textAlignment; + } + + /** + * Set textAlignment. + * + * @param string $textAlignment + * + * @return self + */ + public function setTextAlignment($textAlignment) + { + TextAlignment::validate($textAlignment); + $this->textAlignment = $textAlignment; return $this; } + + /** + * @return bool + */ + public function hasSuppressAutoHyphens() + { + return $this->suppressAutoHyphens; + } + + /** + * @param bool $suppressAutoHyphens + */ + public function setSuppressAutoHyphens($suppressAutoHyphens): void + { + $this->suppressAutoHyphens = (bool) $suppressAutoHyphens; + } } diff --git a/src/PhpWord/Style/Row.php b/src/PhpWord/Style/Row.php index 45897aed5e..749839c4e2 100644 --- a/src/PhpWord/Style/Row.php +++ b/src/PhpWord/Style/Row.php @@ -1,4 +1,5 @@ tblHeader; } /** - * Is tblHeader - * - * @param bool $value - * @return self + * Is tblHeader. */ - public function setTblHeader($value = true) + public function setTblHeader(bool $value = true): self { $this->tblHeader = $this->setBoolVal($value, $this->tblHeader); @@ -76,22 +72,17 @@ public function setTblHeader($value = true) } /** - * Is cantSplit - * - * @return bool + * Is cantSplit. */ - public function isCantSplit() + public function isCantSplit(): bool { return $this->cantSplit; } /** - * Is cantSplit - * - * @param bool $value - * @return self + * Is cantSplit. */ - public function setCantSplit($value = true) + public function setCantSplit(bool $value = true): self { $this->cantSplit = $this->setBoolVal($value, $this->cantSplit); @@ -99,58 +90,20 @@ public function setCantSplit($value = true) } /** - * Is exactHeight - * - * @return bool + * Is exactHeight. */ - public function isExactHeight() + public function isExactHeight(): bool { return $this->exactHeight; } /** - * Set exactHeight - * - * @param bool $value - * @return self + * Set exactHeight. */ - public function setExactHeight($value = true) + public function setExactHeight(bool $value = true): self { $this->exactHeight = $this->setBoolVal($value, $this->exactHeight); return $this; } - - /** - * Get tblHeader - * - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getTblHeader() - { - return $this->isTblHeader(); - } - - /** - * Get cantSplit - * - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getCantSplit() - { - return $this->isCantSplit(); - } - - /** - * Get exactHeight - * - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getExactHeight() - { - return $this->isExactHeight(); - } } diff --git a/src/PhpWord/Style/Section.php b/src/PhpWord/Style/Section.php index 128e8e6b21..1f8e1f5c41 100644 --- a/src/PhpWord/Style/Section.php +++ b/src/PhpWord/Style/Section.php @@ -1,4 +1,5 @@ paper === null) { $this->paper = new Paper(); } @@ -203,10 +193,11 @@ public function setPaperSize($value = 'A4') } /** - * Set Setting Value + * Set Setting Value. * * @param string $key - * @param string $value + * @param array|int|string $value + * * @return self */ public function setSettingValue($key, $value) @@ -215,20 +206,21 @@ public function setSettingValue($key, $value) } /** - * Set orientation + * Set orientation. * * @param string $value + * * @return self */ public function setOrientation($value = null) { - $enum = array(self::ORIENTATION_PORTRAIT, self::ORIENTATION_LANDSCAPE); + $enum = [self::ORIENTATION_PORTRAIT, self::ORIENTATION_LANDSCAPE]; $this->orientation = $this->setEnumVal($value, $enum, $this->orientation); - /** @var int|float $longSide Type hint */ + /** @var float|int $longSide Type hint */ $longSide = $this->pageSizeW >= $this->pageSizeH ? $this->pageSizeW : $this->pageSizeH; - /** @var int|float $shortSide Type hint */ + /** @var float|int $shortSide Type hint */ $shortSide = $this->pageSizeW < $this->pageSizeH ? $this->pageSizeW : $this->pageSizeH; if ($this->orientation == self::ORIENTATION_PORTRAIT) { @@ -243,7 +235,7 @@ public function setOrientation($value = null) } /** - * Get Page Orientation + * Get Page Orientation. * * @return string */ @@ -253,7 +245,7 @@ public function getOrientation() } /** - * Set Portrait Orientation + * Set Portrait Orientation. * * @return self */ @@ -263,7 +255,7 @@ public function setPortrait() } /** - * Set Landscape Orientation + * Set Landscape Orientation. * * @return self */ @@ -273,9 +265,9 @@ public function setLandscape() } /** - * Get Page Size Width + * Get Page Size Width. * - * @return int|float|null + * @return null|float|int * * @since 0.12.0 */ @@ -285,9 +277,9 @@ public function getPageSizeW() } /** - * @param int|float|null $value + * @param null|float|int $value * - * @return \PhpOffice\PhpWord\Style\Section + * @return Section * * @since 0.12.0 */ @@ -299,9 +291,9 @@ public function setPageSizeW($value = null) } /** - * Get Page Size Height + * Get Page Size Height. * - * @return int|float|null + * @return null|float|int * * @since 0.12.0 */ @@ -311,9 +303,9 @@ public function getPageSizeH() } /** - * @param int|float|null $value + * @param null|float|int $value * - * @return \PhpOffice\PhpWord\Style\Section + * @return Section * * @since 0.12.0 */ @@ -325,101 +317,9 @@ public function setPageSizeH($value = null) } /** - * Get Margin Top - * - * @return int|float - */ - public function getMarginTop() - { - return $this->marginTop; - } - - /** - * Set Margin Top - * - * @param int|float $value - * @return self - */ - public function setMarginTop($value = null) - { - $this->marginTop = $this->setNumericVal($value, self::DEFAULT_MARGIN); - - return $this; - } - - /** - * Get Margin Left - * - * @return int|float - */ - public function getMarginLeft() - { - return $this->marginLeft; - } - - /** - * Set Margin Left - * - * @param int|float $value - * @return self - */ - public function setMarginLeft($value = null) - { - $this->marginLeft = $this->setNumericVal($value, self::DEFAULT_MARGIN); - - return $this; - } - - /** - * Get Margin Right - * - * @return int|float - */ - public function getMarginRight() - { - return $this->marginRight; - } - - /** - * Set Margin Right - * - * @param int|float $value - * @return self - */ - public function setMarginRight($value = null) - { - $this->marginRight = $this->setNumericVal($value, self::DEFAULT_MARGIN); - - return $this; - } - - /** - * Get Margin Bottom - * - * @return int|float - */ - public function getMarginBottom() - { - return $this->marginBottom; - } - - /** - * Set Margin Bottom + * Get gutter. * - * @param int|float $value - * @return self - */ - public function setMarginBottom($value = null) - { - $this->marginBottom = $this->setNumericVal($value, self::DEFAULT_MARGIN); - - return $this; - } - - /** - * Get gutter - * - * @return int|float + * @return float|int */ public function getGutter() { @@ -427,9 +327,10 @@ public function getGutter() } /** - * Set gutter + * Set gutter. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setGutter($value = null) @@ -440,9 +341,9 @@ public function setGutter($value = null) } /** - * Get Header Height + * Get Header Height. * - * @return int|float + * @return float|int */ public function getHeaderHeight() { @@ -450,9 +351,10 @@ public function getHeaderHeight() } /** - * Set Header Height + * Set Header Height. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setHeaderHeight($value = null) @@ -463,9 +365,9 @@ public function setHeaderHeight($value = null) } /** - * Get Footer Height + * Get Footer Height. * - * @return int|float + * @return float|int */ public function getFooterHeight() { @@ -473,9 +375,10 @@ public function getFooterHeight() } /** - * Set Footer Height + * Set Footer Height. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setFooterHeight($value = null) @@ -486,7 +389,7 @@ public function setFooterHeight($value = null) } /** - * Get page numbering start + * Get page numbering start. * * @return null|int */ @@ -496,19 +399,21 @@ public function getPageNumberingStart() } /** - * Set page numbering start + * Set page numbering start. * * @param null|int $pageNumberingStart + * * @return self */ public function setPageNumberingStart($pageNumberingStart = null) { $this->pageNumberingStart = $pageNumberingStart; + return $this; } /** - * Get Section Columns Count + * Get Section Columns Count. * * @return int */ @@ -518,9 +423,10 @@ public function getColsNum() } /** - * Set Section Columns Count + * Set Section Columns Count. * * @param int $value + * * @return self */ public function setColsNum($value = null) @@ -531,9 +437,9 @@ public function setColsNum($value = null) } /** - * Get Section Space Between Columns + * Get Section Space Between Columns. * - * @return int|float + * @return float|int */ public function getColsSpace() { @@ -541,9 +447,10 @@ public function getColsSpace() } /** - * Set Section Space Between Columns + * Set Section Space Between Columns. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setColsSpace($value = null) @@ -554,9 +461,9 @@ public function setColsSpace($value = null) } /** - * Get Break Type + * Get Break Type. * - * @return string + * @return ?string */ public function getBreakType() { @@ -564,21 +471,23 @@ public function getBreakType() } /** - * Set Break Type + * Set Break Type. * * @param string $value + * * @return self */ public function setBreakType($value = null) { $this->breakType = $value; + return $this; } /** - * Get line numbering + * Get line numbering. * - * @return \PhpOffice\PhpWord\Style\LineNumbering + * @return LineNumbering */ public function getLineNumbering() { @@ -586,9 +495,10 @@ public function getLineNumbering() } /** - * Set line numbering + * Set line numbering. * * @param mixed $value + * * @return self */ public function setLineNumbering($value = null) @@ -597,4 +507,29 @@ public function setLineNumbering($value = null) return $this; } + + /** + * Get vertical alignment. + * + * @return ?string + */ + public function getVAlign() + { + return $this->vAlign; + } + + /** + * Set vertical alignment. + * + * @param string $value + * + * @return self + */ + public function setVAlign($value = null) + { + VerticalJc::validate($value); + $this->vAlign = $value; + + return $this; + } } diff --git a/src/PhpWord/Style/Shading.php b/src/PhpWord/Style/Shading.php index 5c9742c962..739e56e9de 100644 --- a/src/PhpWord/Style/Shading.php +++ b/src/PhpWord/Style/Shading.php @@ -1,4 +1,5 @@ setStyleByArray($style); } /** - * Get pattern + * Get pattern. * * @return string */ @@ -82,24 +85,25 @@ public function getPattern() } /** - * Set pattern + * Set pattern. * * @param string $value + * * @return self */ public function setPattern($value = null) { - $enum = array( + $enum = [ self::PATTERN_CLEAR, self::PATTERN_SOLID, self::PATTERN_HSTRIPE, - self::PATTERN_VSTRIPE, self::PATTERN_DSTRIPE, self::PATTERN_HCROSS, self::PATTERN_DCROSS - ); + self::PATTERN_VSTRIPE, self::PATTERN_DSTRIPE, self::PATTERN_HCROSS, self::PATTERN_DCROSS, + ]; $this->pattern = $this->setEnumVal($value, $enum, $this->pattern); return $this; } /** - * Get color + * Get color. * * @return string */ @@ -109,9 +113,10 @@ public function getColor() } /** - * Set pattern + * Set pattern. * * @param string $value + * * @return self */ public function setColor($value = null) @@ -122,7 +127,7 @@ public function setColor($value = null) } /** - * Get fill + * Get fill. * * @return string */ @@ -132,9 +137,10 @@ public function getFill() } /** - * Set fill + * Set fill. * * @param string $value + * * @return self */ public function setFill($value = null) diff --git a/src/PhpWord/Style/Shadow.php b/src/PhpWord/Style/Shadow.php index deafbff0f8..2cdf058117 100644 --- a/src/PhpWord/Style/Shadow.php +++ b/src/PhpWord/Style/Shadow.php @@ -1,4 +1,5 @@ setStyleByArray($style); } /** - * Get color + * Get color. * * @return string */ @@ -60,9 +61,10 @@ public function getColor() } /** - * Set color + * Set color. * * @param string $value + * * @return self */ public function setColor($value = null) @@ -73,7 +75,7 @@ public function setColor($value = null) } /** - * Get offset + * Get offset. * * @return string */ @@ -83,9 +85,10 @@ public function getOffset() } /** - * Set offset + * Set offset. * * @param string $value + * * @return self */ public function setOffset($value = null) diff --git a/src/PhpWord/Style/Shape.php b/src/PhpWord/Style/Shape.php index c9809920bb..9f47ff7feb 100644 --- a/src/PhpWord/Style/Shape.php +++ b/src/PhpWord/Style/Shape.php @@ -1,4 +1,5 @@ setStyleByArray($style); } /** - * Get points + * Get points. * * @return string */ @@ -103,9 +105,10 @@ public function getPoints() } /** - * Set points + * Set points. * * @param string $value + * * @return self */ public function setPoints($value = null) @@ -116,9 +119,9 @@ public function setPoints($value = null) } /** - * Get roundness + * Get roundness. * - * @return int|float + * @return float|int */ public function getRoundness() { @@ -126,9 +129,10 @@ public function getRoundness() } /** - * Set roundness + * Set roundness. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setRoundness($value = null) @@ -139,9 +143,9 @@ public function setRoundness($value = null) } /** - * Get frame + * Get frame. * - * @return \PhpOffice\PhpWord\Style\Frame + * @return Frame */ public function getFrame() { @@ -149,9 +153,10 @@ public function getFrame() } /** - * Set frame + * Set frame. * * @param mixed $value + * * @return self */ public function setFrame($value = null) @@ -162,9 +167,9 @@ public function setFrame($value = null) } /** - * Get fill + * Get fill. * - * @return \PhpOffice\PhpWord\Style\Fill + * @return Fill */ public function getFill() { @@ -172,9 +177,10 @@ public function getFill() } /** - * Set fill + * Set fill. * * @param mixed $value + * * @return self */ public function setFill($value = null) @@ -185,9 +191,9 @@ public function setFill($value = null) } /** - * Get outline + * Get outline. * - * @return \PhpOffice\PhpWord\Style\Outline + * @return Outline */ public function getOutline() { @@ -195,9 +201,10 @@ public function getOutline() } /** - * Set outline + * Set outline. * * @param mixed $value + * * @return self */ public function setOutline($value = null) @@ -208,9 +215,9 @@ public function setOutline($value = null) } /** - * Get shadow + * Get shadow. * - * @return \PhpOffice\PhpWord\Style\Shadow + * @return Shadow */ public function getShadow() { @@ -218,9 +225,10 @@ public function getShadow() } /** - * Set shadow + * Set shadow. * * @param mixed $value + * * @return self */ public function setShadow($value = null) @@ -231,9 +239,9 @@ public function setShadow($value = null) } /** - * Get 3D extrusion + * Get 3D extrusion. * - * @return \PhpOffice\PhpWord\Style\Extrusion + * @return Extrusion */ public function getExtrusion() { @@ -241,9 +249,10 @@ public function getExtrusion() } /** - * Set 3D extrusion + * Set 3D extrusion. * * @param mixed $value + * * @return self */ public function setExtrusion($value = null) diff --git a/src/PhpWord/Style/Spacing.php b/src/PhpWord/Style/Spacing.php index 9c9f3a8167..2ffa40620c 100644 --- a/src/PhpWord/Style/Spacing.php +++ b/src/PhpWord/Style/Spacing.php @@ -1,4 +1,5 @@ setStyleByArray($style); } /** - * Get before + * Get before. * - * @return int|float + * @return null|float|int */ public function getBefore() { @@ -74,9 +77,10 @@ public function getBefore() } /** - * Set before + * Set before. + * + * @param null|float|int $value * - * @param int|float $value * @return self */ public function setBefore($value = null) @@ -87,9 +91,9 @@ public function setBefore($value = null) } /** - * Get after + * Get after. * - * @return int|float + * @return null|float|int */ public function getAfter() { @@ -97,9 +101,10 @@ public function getAfter() } /** - * Set after + * Set after. + * + * @param null|float|int $value * - * @param int|float $value * @return self */ public function setAfter($value = null) @@ -110,9 +115,9 @@ public function setAfter($value = null) } /** - * Get line + * Get line. * - * @return int|float + * @return null|float|int */ public function getLine() { @@ -120,9 +125,10 @@ public function getLine() } /** - * Set distance + * Set distance. + * + * @param null|float|int $value * - * @param int|float $value * @return self */ public function setLine($value = null) @@ -133,24 +139,26 @@ public function setLine($value = null) } /** - * Get line rule + * Get line rule. * * @return string */ - public function getRule() + public function getLineRule() { - return $this->rule; + return $this->lineRule; } /** - * Set line rule + * Set line rule. * * @param string $value + * * @return self */ - public function setRule($value = null) + public function setLineRule($value = null) { - $this->rule = $value; + LineSpacingRule::validate($value); + $this->lineRule = $value; return $this; } diff --git a/src/PhpWord/Style/TOC.php b/src/PhpWord/Style/TOC.php index 5285565278..1efcb6e474 100644 --- a/src/PhpWord/Style/TOC.php +++ b/src/PhpWord/Style/TOC.php @@ -1,4 +1,5 @@ type = $this->setEnumVal($type, $stopTypes, $this->type); $this->position = $this->setNumericVal($position, $this->position); @@ -94,7 +95,7 @@ public function __construct($type = null, $position = 0, $leader = null) } /** - * Get stop type + * Get stop type. * * @return string */ @@ -104,25 +105,26 @@ public function getType() } /** - * Set stop type + * Set stop type. * * @param string $value + * * @return self */ public function setType($value) { - $enum = array( + $enum = [ self::TAB_STOP_CLEAR, self::TAB_STOP_LEFT, self::TAB_STOP_CENTER, self::TAB_STOP_RIGHT, self::TAB_STOP_DECIMAL, self::TAB_STOP_BAR, self::TAB_STOP_NUM, - ); + ]; $this->type = $this->setEnumVal($value, $enum, $this->type); return $this; } /** - * Get leader + * Get leader. * * @return string */ @@ -132,26 +134,27 @@ public function getLeader() } /** - * Set leader + * Set leader. * * @param string $value + * * @return self */ public function setLeader($value) { - $enum = array( + $enum = [ self::TAB_LEADER_NONE, self::TAB_LEADER_DOT, self::TAB_LEADER_HYPHEN, self::TAB_LEADER_UNDERSCORE, self::TAB_LEADER_HEAVY, self::TAB_LEADER_MIDDLEDOT, - ); + ]; $this->leader = $this->setEnumVal($value, $enum, $this->leader); return $this; } /** - * Get position + * Get position. * - * @return int|float + * @return float|int */ public function getPosition() { @@ -159,9 +162,10 @@ public function getPosition() } /** - * Set position + * Set position. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setPosition($value) diff --git a/src/PhpWord/Style/Table.php b/src/PhpWord/Style/Table.php index 24f5066777..2510a3fa72 100644 --- a/src/PhpWord/Style/Table.php +++ b/src/PhpWord/Style/Table.php @@ -1,4 +1,5 @@ alignment = new Alignment(); + private $position; + + /** @var null|TblWidthComplexType */ + private $indent; + + /** + * The width of each column, computed based on the max cell width of each column. + * + * @var int[] + */ + private $columnWidths; + + /** + * Visually Right to Left Table. + * + * @see http://www.datypic.com/sc/ooxml/e-w_bidiVisual-1.html + * + * @var ?bool + */ + private $bidiVisual; + /** + * Create new table style. + */ + public function __construct(?array $tableStyle = null, ?array $firstRowStyle = null) + { // Clone first row from table style, but with certain properties disabled - if ($firstRowStyle !== null && is_array($firstRowStyle)) { + if ($firstRowStyle !== null) { $this->firstRowStyle = clone $this; $this->firstRowStyle->isFirstRow = true; - unset($this->firstRowStyle->firstRowStyle); - unset($this->firstRowStyle->borderInsideHSize); - unset($this->firstRowStyle->borderInsideHColor); - unset($this->firstRowStyle->borderInsideVSize); - unset($this->firstRowStyle->borderInsideVColor); - unset($this->firstRowStyle->cellMarginTop); - unset($this->firstRowStyle->cellMarginLeft); - unset($this->firstRowStyle->cellMarginRight); - unset($this->firstRowStyle->cellMarginBottom); + unset( + $this->firstRowStyle->firstRowStyle, + $this->firstRowStyle->borderInsideHSize, + $this->firstRowStyle->borderInsideHColor, + $this->firstRowStyle->borderInsideVSize, + $this->firstRowStyle->borderInsideVColor, + $this->firstRowStyle->cellMarginTop, + $this->firstRowStyle->cellMarginLeft, + $this->firstRowStyle->cellMarginRight, + $this->firstRowStyle->cellMarginBottom, + $this->firstRowStyle->cellSpacing + ); $this->firstRowStyle->setStyleByArray($firstRowStyle); } - if ($tableStyle !== null && is_array($tableStyle)) { + if ($tableStyle !== null) { $this->setStyleByArray($tableStyle); } } /** - * Set first row + * @param null|float|int $cellSpacing + */ + public function setCellSpacing($cellSpacing = null): self + { + $this->cellSpacing = $cellSpacing; + + return $this; + } + + /** + * @return null|float|int + */ + public function getCellSpacing() + { + return $this->cellSpacing; + } + + /** + * Set first row. * - * @return \PhpOffice\PhpWord\Style\Table + * @return Table */ public function getFirstRow() { @@ -163,9 +226,9 @@ public function getFirstRow() } /** - * Get background + * Get background. * - * @return string + * @return ?string */ public function getBgColor() { @@ -177,39 +240,41 @@ public function getBgColor() } /** - * Set background + * Set background. * * @param string $value + * * @return self */ public function setBgColor($value = null) { - $this->setShading(array('fill' => $value)); + $this->setShading(['fill' => $value]); return $this; } /** - * Get TLRBHV Border Size + * Get TLRBHV Border Size. * - * @return integer[] + * @return int[] */ public function getBorderSize() { - return array( + return [ $this->getBorderTopSize(), $this->getBorderLeftSize(), $this->getBorderRightSize(), $this->getBorderBottomSize(), $this->getBorderInsideHSize(), $this->getBorderInsideVSize(), - ); + ]; } /** - * Set TLRBHV Border Size + * Set TLRBHV Border Size. * * @param int $value Border size in eighths of a point (1/8 point) + * * @return self */ public function setBorderSize($value = null) @@ -225,26 +290,27 @@ public function setBorderSize($value = null) } /** - * Get TLRBHV Border Color + * Get TLRBHV Border Color. * * @return string[] */ public function getBorderColor() { - return array( + return [ $this->getBorderTopColor(), $this->getBorderLeftColor(), $this->getBorderRightColor(), $this->getBorderBottomColor(), $this->getBorderInsideHColor(), $this->getBorderInsideVColor(), - ); + ]; } /** - * Set TLRBHV Border Color + * Set TLRBHV Border Color. * * @param string $value + * * @return self */ public function setBorderColor($value = null) @@ -260,7 +326,7 @@ public function setBorderColor($value = null) } /** - * Get border size inside horizontal + * Get border size inside horizontal. * * @return int */ @@ -270,9 +336,10 @@ public function getBorderInsideHSize() } /** - * Set border size inside horizontal + * Set border size inside horizontal. * * @param int $value + * * @return self */ public function setBorderInsideHSize($value = null) @@ -281,7 +348,7 @@ public function setBorderInsideHSize($value = null) } /** - * Get border color inside horizontal + * Get border color inside horizontal. * * @return string */ @@ -291,9 +358,10 @@ public function getBorderInsideHColor() } /** - * Set border color inside horizontal + * Set border color inside horizontal. * * @param string $value + * * @return self */ public function setBorderInsideHColor($value = null) @@ -302,7 +370,7 @@ public function setBorderInsideHColor($value = null) } /** - * Get border size inside vertical + * Get border size inside vertical. * * @return int */ @@ -312,9 +380,10 @@ public function getBorderInsideVSize() } /** - * Set border size inside vertical + * Set border size inside vertical. * * @param int $value + * * @return self */ public function setBorderInsideVSize($value = null) @@ -323,7 +392,7 @@ public function setBorderInsideVSize($value = null) } /** - * Get border color inside vertical + * Get border color inside vertical. * * @return string */ @@ -333,9 +402,10 @@ public function getBorderInsideVColor() } /** - * Set border color inside vertical + * Set border color inside vertical. * * @param string $value + * * @return self */ public function setBorderInsideVColor($value = null) @@ -344,7 +414,7 @@ public function setBorderInsideVColor($value = null) } /** - * Get cell margin top + * Get cell margin top. * * @return int */ @@ -354,9 +424,10 @@ public function getCellMarginTop() } /** - * Set cell margin top + * Set cell margin top. * * @param int $value + * * @return self */ public function setCellMarginTop($value = null) @@ -365,7 +436,7 @@ public function setCellMarginTop($value = null) } /** - * Get cell margin left + * Get cell margin left. * * @return int */ @@ -375,9 +446,10 @@ public function getCellMarginLeft() } /** - * Set cell margin left + * Set cell margin left. * * @param int $value + * * @return self */ public function setCellMarginLeft($value = null) @@ -386,7 +458,7 @@ public function setCellMarginLeft($value = null) } /** - * Get cell margin right + * Get cell margin right. * * @return int */ @@ -396,9 +468,10 @@ public function getCellMarginRight() } /** - * Set cell margin right + * Set cell margin right. * * @param int $value + * * @return self */ public function setCellMarginRight($value = null) @@ -407,7 +480,7 @@ public function setCellMarginRight($value = null) } /** - * Get cell margin bottom + * Get cell margin bottom. * * @return int */ @@ -417,9 +490,10 @@ public function getCellMarginBottom() } /** - * Set cell margin bottom + * Set cell margin bottom. * * @param int $value + * * @return self */ public function setCellMarginBottom($value = null) @@ -428,24 +502,25 @@ public function setCellMarginBottom($value = null) } /** - * Get cell margin + * Get cell margin. * - * @return integer[] + * @return int[] */ public function getCellMargin() { - return array( + return [ $this->cellMarginTop, $this->cellMarginLeft, $this->cellMarginRight, - $this->cellMarginBottom - ); + $this->cellMarginBottom, + ]; } /** - * Set TLRB cell margin + * Set TLRB cell margin. * * @param int $value Margin in twips + * * @return self */ public function setCellMargin($value = null) @@ -459,7 +534,7 @@ public function setCellMargin($value = null) } /** - * Check if any of the margin is not null + * Check if any of the margin is not null. * * @return bool */ @@ -471,9 +546,9 @@ public function hasMargin() } /** - * Get shading + * Get shading. * - * @return \PhpOffice\PhpWord\Style\Shading + * @return Shading */ public function getShading() { @@ -481,9 +556,10 @@ public function getShading() } /** - * Set shading + * Set shading. * * @param mixed $value + * * @return self */ public function setShading($value = null) @@ -494,32 +570,35 @@ public function setShading($value = null) } /** - * Get alignment + * @since 0.13.0 * * @return string */ - public function getAlign() + public function getAlignment() { - return $this->alignment->getValue(); + return $this->alignment; } /** - * Set alignment + * @since 0.13.0 * * @param string $value + * * @return self */ - public function setAlign($value = null) + public function setAlignment($value) { - $this->alignment->setValue($value); + if (JcTable::isValid($value) || Jc::isValid($value)) { + $this->alignment = $value; + } return $this; } /** - * Get width + * Get width. * - * @return int|float + * @return float|int */ public function getWidth() { @@ -527,9 +606,10 @@ public function getWidth() } /** - * Set width + * Set width. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setWidth($value = null) @@ -540,7 +620,7 @@ public function setWidth($value = null) } /** - * Get width unit + * Get width unit. * * @return string */ @@ -550,31 +630,58 @@ public function getUnit() } /** - * Set width unit + * Set width unit. * * @param string $value + * * @return self */ public function setUnit($value = null) { - $enum = array(self::WIDTH_AUTO, self::WIDTH_PERCENT, self::WIDTH_TWIP); - $this->unit = $this->setEnumVal($value, $enum, $this->unit); + TblWidth::validate($value); + $this->unit = $value; return $this; } /** - * Get table style only property by checking if it's a firstRow + * Get layout. + * + * @return string + */ + public function getLayout() + { + return $this->layout; + } + + /** + * Set layout. + * + * @param string $value + * + * @return self + */ + public function setLayout($value = null) + { + $enum = [self::LAYOUT_AUTO, self::LAYOUT_FIXED]; + $this->layout = $this->setEnumVal($value, $enum, $this->layout); + + return $this; + } + + /** + * Get table style only property by checking if it's a firstRow. * * This is necessary since firstRow style is cloned from table style but * without certain properties activated, e.g. margins * * @param string $property - * @return int|string|null + * + * @return null|int|string */ private function getTableOnlyProperty($property) { - if ($this->isFirstRow === false) { + if (false === $this->isFirstRow) { return $this->$property; } @@ -582,7 +689,7 @@ private function getTableOnlyProperty($property) } /** - * Set table style only property by checking if it's a firstRow + * Set table style only property by checking if it's a firstRow. * * This is necessary since firstRow style is cloned from table style but * without certain properties activated, e.g. margins @@ -590,12 +697,13 @@ private function getTableOnlyProperty($property) * @param string $property * @param int|string $value * @param bool $isNumeric + * * @return self */ private function setTableOnlyProperty($property, $value, $isNumeric = true) { - if ($this->isFirstRow === false) { - if ($isNumeric === true) { + if (false === $this->isFirstRow) { + if (true === $isNumeric) { $this->$property = $this->setNumericVal($value, $this->$property); } else { $this->$property = $value; @@ -604,4 +712,93 @@ private function setTableOnlyProperty($property, $value, $isNumeric = true) return $this; } + + /** + * Get position. + * + * @return ?TablePosition + */ + public function getPosition() + { + return $this->position; + } + + /** + * Set position. + * + * @param mixed $value + * + * @return self + */ + public function setPosition($value = null) + { + $this->setObjectVal($value, 'TablePosition', $this->position); + + return $this; + } + + /** + * @return ?TblWidthComplexType + */ + public function getIndent() + { + return $this->indent; + } + + /** + * @return self + * + * @see http://www.datypic.com/sc/ooxml/e-w_tblInd-1.html + */ + public function setIndent(TblWidthComplexType $indent) + { + $this->indent = $indent; + + return $this; + } + + /** + * Get the columnWidths. + * + * @return null|int[] + */ + public function getColumnWidths() + { + return $this->columnWidths; + } + + /** + * The column widths. + * + * @param int[] $value + */ + public function setColumnWidths(?array $value = null): void + { + $this->columnWidths = $value; + } + + /** + * Get bidiVisual. + * + * @return ?bool + */ + public function isBidiVisual() + { + return $this->bidiVisual ?? Settings::isDefaultRtl(); + } + + /** + * Set bidiVisual. + * + * @param ?bool $bidi + * Set to true to visually present table as Right to Left + * + * @return self + */ + public function setBidiVisual($bidi) + { + $this->bidiVisual = $bidi; + + return $this; + } } diff --git a/src/PhpWord/Style/TablePosition.php b/src/PhpWord/Style/TablePosition.php new file mode 100644 index 0000000000..deec49dfe3 --- /dev/null +++ b/src/PhpWord/Style/TablePosition.php @@ -0,0 +1,429 @@ +setStyleByArray($style); + } + + /** + * Get distance from left of table to text. + * + * @return int + */ + public function getLeftFromText() + { + return $this->leftFromText; + } + + /** + * Set distance from left of table to text. + * + * @param int $value + * + * @return self + */ + public function setLeftFromText($value = null) + { + $this->leftFromText = $this->setNumericVal($value, $this->leftFromText); + + return $this; + } + + /** + * Get distance from right of table to text. + * + * @return int + */ + public function getRightFromText() + { + return $this->rightFromText; + } + + /** + * Set distance from right of table to text. + * + * @param int $value + * + * @return self + */ + public function setRightFromText($value = null) + { + $this->rightFromText = $this->setNumericVal($value, $this->rightFromText); + + return $this; + } + + /** + * Get distance from top of table to text. + * + * @return int + */ + public function getTopFromText() + { + return $this->topFromText; + } + + /** + * Set distance from top of table to text. + * + * @param int $value + * + * @return self + */ + public function setTopFromText($value = null) + { + $this->topFromText = $this->setNumericVal($value, $this->topFromText); + + return $this; + } + + /** + * Get distance from bottom of table to text. + * + * @return int + */ + public function getBottomFromText() + { + return $this->bottomFromText; + } + + /** + * Set distance from bottom of table to text. + * + * @param int $value + * + * @return self + */ + public function setBottomFromText($value = null) + { + $this->bottomFromText = $this->setNumericVal($value, $this->bottomFromText); + + return $this; + } + + /** + * Get table vertical anchor. + * + * @return string + */ + public function getVertAnchor() + { + return $this->vertAnchor; + } + + /** + * Set table vertical anchor. + * + * @param string $value + * + * @return self + */ + public function setVertAnchor($value = null) + { + $enum = [ + self::VANCHOR_TEXT, + self::VANCHOR_MARGIN, + self::VANCHOR_PAGE, + ]; + $this->vertAnchor = $this->setEnumVal($value, $enum, $this->vertAnchor); + + return $this; + } + + /** + * Get table horizontal anchor. + * + * @return string + */ + public function getHorzAnchor() + { + return $this->horzAnchor; + } + + /** + * Set table horizontal anchor. + * + * @param string $value + * + * @return self + */ + public function setHorzAnchor($value = null) + { + $enum = [ + self::HANCHOR_TEXT, + self::HANCHOR_MARGIN, + self::HANCHOR_PAGE, + ]; + $this->horzAnchor = $this->setEnumVal($value, $enum, $this->horzAnchor); + + return $this; + } + + /** + * Get relative horizontal alignment from anchor. + * + * @return string + */ + public function getTblpXSpec() + { + return $this->tblpXSpec; + } + + /** + * Set relative horizontal alignment from anchor. + * + * @param string $value + * + * @return self + */ + public function setTblpXSpec($value = null) + { + $enum = [ + self::XALIGN_LEFT, + self::XALIGN_CENTER, + self::XALIGN_RIGHT, + self::XALIGN_INSIDE, + self::XALIGN_OUTSIDE, + ]; + $this->tblpXSpec = $this->setEnumVal($value, $enum, $this->tblpXSpec); + + return $this; + } + + /** + * Get absolute horizontal distance from anchor. + * + * @return int + */ + public function getTblpX() + { + return $this->tblpX; + } + + /** + * Set absolute horizontal distance from anchor. + * + * @param int $value + * + * @return self + */ + public function setTblpX($value = null) + { + $this->tblpX = $this->setNumericVal($value, $this->tblpX); + + return $this; + } + + /** + * Get relative vertical alignment from anchor. + * + * @return string + */ + public function getTblpYSpec() + { + return $this->tblpYSpec; + } + + /** + * Set relative vertical alignment from anchor. + * + * @param string $value + * + * @return self + */ + public function setTblpYSpec($value = null) + { + $enum = [ + self::YALIGN_INLINE, + self::YALIGN_TOP, + self::YALIGN_CENTER, + self::YALIGN_BOTTOM, + self::YALIGN_INSIDE, + self::YALIGN_OUTSIDE, + ]; + $this->tblpYSpec = $this->setEnumVal($value, $enum, $this->tblpYSpec); + + return $this; + } + + /** + * Get absolute vertical distance from anchor. + * + * @return int + */ + public function getTblpY() + { + return $this->tblpY; + } + + /** + * Set absolute vertical distance from anchor. + * + * @param int $value + * + * @return self + */ + public function setTblpY($value = null) + { + $this->tblpY = $this->setNumericVal($value, $this->tblpY); + + return $this; + } +} diff --git a/src/PhpWord/Style/TextBox.php b/src/PhpWord/Style/TextBox.php index 6220b74096..82fe7a95c4 100644 --- a/src/PhpWord/Style/TextBox.php +++ b/src/PhpWord/Style/TextBox.php @@ -1,151 +1,153 @@ bgColor = $value; + } + + /** + * Get background color. + */ + public function getBgColor(): ?string + { + return $this->bgColor; + } + + /** + * Set margin top. */ - public function setInnerMarginTop($value = null) + public function setInnerMarginTop(?int $value = null): void { $this->innerMarginTop = $value; } /** - * Get margin top - * - * @return int + * Get margin top. */ - public function getInnerMarginTop() + public function getInnerMarginTop(): ?int { return $this->innerMarginTop; } /** * Set margin left. - * - * @param int $value - * @return void */ - public function setInnerMarginLeft($value = null) + public function setInnerMarginLeft(?int $value = null): void { $this->innerMarginLeft = $value; } /** - * Get margin left - * - * @return int + * Get margin left. */ - public function getInnerMarginLeft() + public function getInnerMarginLeft(): ?int { return $this->innerMarginLeft; } /** * Set margin right. - * - * @param int $value - * @return void */ - public function setInnerMarginRight($value = null) + public function setInnerMarginRight(?int $value = null): void { $this->innerMarginRight = $value; } /** - * Get margin right - * - * @return int + * Get margin right. */ - public function getInnerMarginRight() + public function getInnerMarginRight(): ?int { return $this->innerMarginRight; } /** * Set margin bottom. - * - * @param int $value - * @return void */ - public function setInnerMarginBottom($value = null) + public function setInnerMarginBottom(?int $value = null): void { $this->innerMarginBottom = $value; } /** - * Get margin bottom - * - * @return int + * Get margin bottom. */ - public function getInnerMarginBottom() + public function getInnerMarginBottom(): ?int { return $this->innerMarginBottom; } @@ -153,10 +155,9 @@ public function getInnerMarginBottom() /** * Set TLRB cell margin. * - * @param int $value Margin in twips - * @return void + * @param null|int $value Margin in twips */ - public function setInnerMargin($value = null) + public function setInnerMargin(?int $value = null): void { $this->setInnerMarginTop($value); $this->setInnerMarginLeft($value); @@ -165,25 +166,24 @@ public function setInnerMargin($value = null) } /** - * Get cell margin + * Get cell margin. * - * @return integer[] + * @return int[] */ - public function getInnerMargin() + public function getInnerMargin(): array { - return array($this->innerMarginLeft, $this->innerMarginTop, $this->innerMarginRight, $this->innerMarginBottom); + 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(); - for ($i = 0; $i < count($margins); $i++) { + $numMargins = count($margins); + for ($i = 0; $i < $numMargins; ++$i) { if ($margins[$i] !== null) { $hasInnerMargins = true; } @@ -195,41 +195,33 @@ public function hasInnerMargins() /** * Set border size. * - * @param int $value Size in points - * @return void + * @param null|int $value Size in points */ - public function setBorderSize($value = null) + public function setBorderSize(?int $value = null): void { $this->borderSize = $value; } /** - * Get border size - * - * @return int + * Get border size. */ - public function getBorderSize() + public function getBorderSize(): ?int { return $this->borderSize; } /** * Set border color. - * - * @param string $value - * @return void */ - public function setBorderColor($value = null) + public function setBorderColor(?string $value = null): void { $this->borderColor = $value; } /** - * Get border color - * - * @return string + * Get border color. */ - public function getBorderColor() + public function getBorderColor(): ?string { return $this->borderColor; } diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index cbed973011..073393ffc4 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -1,4 +1,5 @@ temporaryDocumentFilename = tempnam(Settings::getTempDir(), 'PhpWord'); - if (false === $this->temporaryDocumentFilename) { - throw new CreateTemporaryFileException(); + $this->tempDocumentFilename = tempnam(Settings::getTempDir(), 'PhpWord'); + if (false === $this->tempDocumentFilename) { + throw new CreateTemporaryFileException(); // @codeCoverageIgnore } // Template file cloning - if (false === copy($documentTemplate, $this->temporaryDocumentFilename)) { - throw new CopyFileException($documentTemplate, $this->temporaryDocumentFilename); + if (false === copy($documentTemplate, $this->tempDocumentFilename)) { + throw new CopyFileException($documentTemplate, $this->tempDocumentFilename); // @codeCoverageIgnore } // Temporary document content extraction $this->zipClass = new ZipArchive(); - $this->zipClass->open($this->temporaryDocumentFilename); + $this->zipClass->open($this->tempDocumentFilename); $index = 1; - while ($this->zipClass->locateName($this->getHeaderName($index)) !== false) { - $this->temporaryDocumentHeaders[$index] = $this->zipClass->getFromName($this->getHeaderName($index)); - $index++; + while (false !== $this->zipClass->locateName($this->getHeaderName($index))) { + $this->tempDocumentHeaders[$index] = $this->readPartWithRels($this->getHeaderName($index)); + ++$index; } $index = 1; - while ($this->zipClass->locateName($this->getFooterName($index)) !== false) { - $this->temporaryDocumentFooters[$index] = $this->zipClass->getFromName($this->getFooterName($index)); - $index++; + while (false !== $this->zipClass->locateName($this->getFooterName($index))) { + $this->tempDocumentFooters[$index] = $this->readPartWithRels($this->getFooterName($index)); + ++$index; + } + + $this->tempDocumentMainPart = $this->readPartWithRels($this->getMainPartName()); + $this->tempDocumentSettingsPart = $this->readPartWithRels($this->getSettingsPartName()); + $tempDocumentContentTypes = $this->zipClass->getFromName($this->getDocumentContentTypesName()); + if (is_string($tempDocumentContentTypes)) { + $this->tempDocumentContentTypes = $tempDocumentContentTypes; + } + } + + public function __destruct() + { + // ZipClass + if ($this->zipClass) { + try { + $this->zipClass->close(); + } catch (Throwable $e) { + // Nothing to do here. + } + } + } + + /** + * Expose zip class. + * + * To replace an image: $templateProcessor->zip()->AddFromString("word/media/image1.jpg", file_get_contents($file));
      + * To read a file: $templateProcessor->zip()->getFromName("word/media/image1.jpg"); + * + * @return ZipArchive + */ + public function zip() + { + return $this->zipClass; + } + + /** + * @param string $fileName + * + * @return string + */ + protected function readPartWithRels($fileName) + { + $relsFileName = $this->getRelationsName($fileName); + $partRelations = $this->zipClass->getFromName($relsFileName); + if ($partRelations !== false) { + $this->tempDocumentRelations[$fileName] = $partRelations; + } + + return $this->fixBrokenMacros($this->zipClass->getFromName($fileName)); + } + + /** + * @param string $xml + * @param XSLTProcessor $xsltProcessor + * + * @return string + */ + protected function transformSingleXml($xml, $xsltProcessor) + { + if (\PHP_VERSION_ID < 80000) { + $orignalLibEntityLoader = libxml_disable_entity_loader(true); + } + $domDocument = new DOMDocument(); + if (false === $domDocument->loadXML($xml)) { + throw new Exception('Could not load the given XML document.'); + } + + $transformedXml = $xsltProcessor->transformToXml($domDocument); + if (false === $transformedXml) { + throw new Exception('Could not transform the given XML document.'); + } + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($orignalLibEntityLoader); } - $this->temporaryDocumentMainPart = $this->zipClass->getFromName('word/document.xml'); + + return $transformedXml; + } + + /** + * @param mixed $xml + * @param XSLTProcessor $xsltProcessor + * + * @return mixed + */ + protected function transformXml($xml, $xsltProcessor) + { + if (is_array($xml)) { + foreach ($xml as &$item) { + $item = $this->transformSingleXml($item, $xsltProcessor); + } + unset($item); + } else { + $xml = $this->transformSingleXml($xml, $xsltProcessor); + } + + return $xml; } /** * Applies XSL style sheet to template's parts. * - * @param \DOMDocument $xslDOMDocument + * Note: since the method doesn't make any guess on logic of the provided XSL style sheet, + * make sure that output is correctly escaped. Otherwise you may get broken document. + * + * @param DOMDocument $xslDomDocument * @param array $xslOptions - * @param string $xslOptionsURI - * @return void - * @throws \PhpOffice\PhpWord\Exception\Exception + * @param string $xslOptionsUri */ - public function applyXslStyleSheet($xslDOMDocument, $xslOptions = array(), $xslOptionsURI = '') + public function applyXslStyleSheet($xslDomDocument, $xslOptions = [], $xslOptionsUri = ''): void { - $xsltProcessor = new \XSLTProcessor(); - - $xsltProcessor->importStylesheet($xslDOMDocument); + $xsltProcessor = new XSLTProcessor(); - if (false === $xsltProcessor->setParameter($xslOptionsURI, $xslOptions)) { + $xsltProcessor->importStylesheet($xslDomDocument); + if (false === $xsltProcessor->setParameter($xslOptionsUri, $xslOptions)) { throw new Exception('Could not set values for the given XSL style sheet parameters.'); } - $xmlDOMDocument = new \DOMDocument(); - if (false === $xmlDOMDocument->loadXML($this->temporaryDocumentMainPart)) { - throw new Exception('Could not load XML from the given template.'); + $this->tempDocumentHeaders = $this->transformXml($this->tempDocumentHeaders, $xsltProcessor); + $this->tempDocumentMainPart = $this->transformXml($this->tempDocumentMainPart, $xsltProcessor); + $this->tempDocumentFooters = $this->transformXml($this->tempDocumentFooters, $xsltProcessor); + } + + /** + * @param string $macro + * + * @return string + */ + protected static function ensureMacroCompleted($macro) + { + if (substr($macro, 0, 2) !== self::$macroOpeningChars && substr($macro, -1) !== self::$macroClosingChars) { + $macro = self::$macroOpeningChars . $macro . self::$macroClosingChars; } - $xmlTransformed = $xsltProcessor->transformToXml($xmlDOMDocument); - if (false === $xmlTransformed) { - throw new Exception('Could not transform the given XML document.'); + return $macro; + } + + /** + * @param ?string $subject + * + * @return string + */ + protected static function ensureUtf8Encoded($subject) + { + return (null !== $subject) ? Text::toUTF8($subject) : ''; + } + + /** + * @param string $search + */ + public function setComplexValue($search, Element\AbstractElement $complexType): void + { + $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); + $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; + + $xmlWriter = new XMLWriter(); + /** @var Writer\Word2007\Element\AbstractElement $elementWriter */ + $elementWriter = new $objectClass($xmlWriter, $complexType, true); + $elementWriter->write(); + + $where = $this->findContainingXmlBlockForMacro($search, 'w:r'); + + if ($where === false) { + return; } - $this->temporaryDocumentMainPart = $xmlTransformed; + $block = $this->getSlice($where['start'], $where['end']); + $textParts = $this->splitTextIntoTexts($block); + $this->replaceXmlBlock($search, $textParts, 'w:r'); + + $search = static::ensureMacroCompleted($search); + $this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:r'); + } + + /** + * @param string $search + */ + public function setComplexBlock($search, Element\AbstractElement $complexType): void + { + $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); + $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; + + $xmlWriter = new XMLWriter(); + /** @var Writer\Word2007\Element\AbstractElement $elementWriter */ + $elementWriter = new $objectClass($xmlWriter, $complexType, false); + $elementWriter->write(); + + $this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:p'); + } + + /** + * @param array|string $search + * @param null|array|bool|float|int|string $replace + * @param int $limit + */ + public function setValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT): void + { + if (is_array($search)) { + foreach ($search as &$item) { + $item = static::ensureMacroCompleted($item); + } + unset($item); + } else { + $search = static::ensureMacroCompleted($search); + } + + if (is_array($replace)) { + foreach ($replace as &$item) { + $item = static::ensureUtf8Encoded($item); + } + unset($item); + } else { + $replace = static::ensureUtf8Encoded(null === $replace ? null : (string) $replace); + } + + if (Settings::isOutputEscapingEnabled()) { + $xmlEscaper = new Xml(); + $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); + } + + /** + * Set values from a one-dimensional array of "variable => value"-pairs. + */ + public function setValues(array $values, int $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT): void + { + foreach ($values as $macro => $replace) { + $this->setValue($macro, $replace, $limit); + } + } + + 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('/()/', '$1"' . $val . '"$2', $block); + + $text = $checked ? '☒' : '☐'; + $block = preg_replace('/().*?(<\/w:t>)/', '$1' . $text . '$2', $block); + + $this->replaceXmlBlock($search, $block, $blockType); + } + + /** + * @param string $search + */ + public function setChart($search, Element\AbstractElement $chart): void + { + $elementName = substr(get_class($chart), strrpos(get_class($chart), '\\') + 1); + $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; + + // Get the next relation id + $rId = $this->getNextRelationsIndex($this->getMainPartName()); + $chart->setRelationId($rId); + + // Define the chart filename + $filename = "charts/chart{$rId}.xml"; + + // Get the part writer + $writerPart = new Writer\Word2007\Part\Chart(); + $writerPart->setElement($chart); + + // ContentTypes.xml + $this->zipClass->addFromString("word/{$filename}", $writerPart->write()); + + // add chart to content type + $xmlRelationsType = ""; + $this->tempDocumentContentTypes = str_replace('', $xmlRelationsType, $this->tempDocumentContentTypes) . ''; + + // Add the chart to relations + $xmlChartRelation = ""; + $this->tempDocumentRelations[$this->getMainPartName()] = str_replace('', $xmlChartRelation, $this->tempDocumentRelations[$this->getMainPartName()]) . ''; + + // Write the chart + $xmlWriter = new XMLWriter(); + $elementWriter = new $objectClass($xmlWriter, $chart, true); + $elementWriter->write(); + + // Place it in the template + $this->replaceXmlBlock($search, '' . $xmlWriter->getData() . '', 'w:p'); + } + + private function getImageArgs($varNameWithArgs) + { + $varElements = explode(':', $varNameWithArgs); + array_shift($varElements); // first element is name of variable => remove it + + $varInlineArgs = []; + // size format documentation: https://msdn.microsoft.com/en-us/library/documentformat.openxml.vml.shape%28v=office.14%29.aspx?f=255&MSPPError=-2147217396 + foreach ($varElements as $argIdx => $varArg) { + if (strpos($varArg, '=')) { // arg=value + [$argName, $argValue] = explode('=', $varArg, 2); + $argName = strtolower($argName); + if ($argName == 'size') { + [$varInlineArgs['width'], $varInlineArgs['height']] = explode('x', $argValue, 2); + } else { + $varInlineArgs[strtolower($argName)] = $argValue; + } + } elseif (preg_match('/^([0-9]*[a-z%]{0,2}|auto)x([0-9]*[a-z%]{0,2}|auto)$/i', $varArg)) { // 60x40 + [$varInlineArgs['width'], $varInlineArgs['height']] = explode('x', $varArg, 2); + } else { // :60:40:f + switch ($argIdx) { + case 0: + $varInlineArgs['width'] = $varArg; + + break; + case 1: + $varInlineArgs['height'] = $varArg; + + break; + case 2: + $varInlineArgs['ratio'] = $varArg; + + break; + } + } + } + + return $varInlineArgs; + } + + private function chooseImageDimension($baseValue, $inlineValue, $defaultValue) + { + $value = $baseValue; + if (null === $value && isset($inlineValue)) { + $value = $inlineValue; + } + if (!preg_match('/^([0-9\.]*(cm|mm|in|pt|pc|px|%|em|ex|)|auto)$/i', $value ?? '')) { + $value = null; + } + if (null === $value) { + $value = $defaultValue; + } + if (is_numeric($value)) { + $value .= 'px'; + } + + return $value; + } + + private function fixImageWidthHeightRatio(&$width, &$height, $actualWidth, $actualHeight): void + { + $imageRatio = $actualWidth / $actualHeight; + + if (($width === '') && ($height === '')) { // defined size are empty + $width = $actualWidth . 'px'; + $height = $actualHeight . 'px'; + } elseif ($width === '') { // defined width is empty + $heightFloat = (float) $height; + $widthFloat = $heightFloat * $imageRatio; + $matches = []; + preg_match('/\\d([a-z%]+)$/', $height, $matches); + $width = $widthFloat . (!empty($matches) ? $matches[1] : 'px'); + } elseif ($height === '') { // defined height is empty + $widthFloat = (float) $width; + $heightFloat = $widthFloat / $imageRatio; + $matches = []; + preg_match('/\\d([a-z%]+)$/', $width, $matches); + $height = $heightFloat . (!empty($matches) ? $matches[1] : 'px'); + } else { // we have defined size, but we need also check it aspect ratio + $widthMatches = []; + preg_match('/\\d([a-z%]+)$/', $width, $widthMatches); + $heightMatches = []; + preg_match('/\\d([a-z%]+)$/', $height, $heightMatches); + // try to fix only if dimensions are same + if (!empty($widthMatches) + && !empty($heightMatches) + && $widthMatches[1] == $heightMatches[1]) { + $dimention = $widthMatches[1]; + $widthFloat = (float) $width; + $heightFloat = (float) $height; + $definedRatio = $widthFloat / $heightFloat; + + if ($imageRatio > $definedRatio) { // image wider than defined box + $height = ($widthFloat / $imageRatio) . $dimention; + } elseif ($imageRatio < $definedRatio) { // image higher than defined box + $width = ($heightFloat * $imageRatio) . $dimention; + } + } + } + } + + private function prepareImageAttrs($replaceImage, $varInlineArgs) + { + // get image path and size + $width = null; + $height = null; + $ratio = null; + + // a closure can be passed as replacement value which after resolving, can contain the replacement info for the image + // use case: only when a image if found, the replacement tags can be generated + if (is_callable($replaceImage)) { + $replaceImage = $replaceImage(); + } + + if (is_array($replaceImage) && isset($replaceImage['path'])) { + $imgPath = $replaceImage['path']; + if (isset($replaceImage['width'])) { + $width = $replaceImage['width']; + } + if (isset($replaceImage['height'])) { + $height = $replaceImage['height']; + } + if (isset($replaceImage['ratio'])) { + $ratio = $replaceImage['ratio']; + } + } else { + $imgPath = $replaceImage; + } + + $width = $this->chooseImageDimension($width, $varInlineArgs['width'] ?? null, 115); + $height = $this->chooseImageDimension($height, $varInlineArgs['height'] ?? null, 70); + + $imageData = @getimagesize($imgPath); + if (!is_array($imageData)) { + throw new Exception(sprintf('Invalid image: %s', $imgPath)); + } + [$actualWidth, $actualHeight, $imageType] = $imageData; + + // fix aspect ratio (by default) + if (null === $ratio && isset($varInlineArgs['ratio'])) { + $ratio = $varInlineArgs['ratio']; + } + if (null === $ratio || !in_array(strtolower($ratio), ['', '-', 'f', 'false'])) { + $this->fixImageWidthHeightRatio($width, $height, $actualWidth, $actualHeight); + } + + $imageAttrs = [ + 'src' => $imgPath, + 'mime' => image_type_to_mime_type($imageType), + 'width' => $width, + 'height' => $height, + ]; + + return $imageAttrs; + } + + private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeType): void + { + // define templates + $typeTpl = ''; + $relationTpl = ''; + $newRelationsTpl = '' . "\n" . ''; + $newRelationsTypeTpl = ''; + $extTransform = [ + 'image/jpeg' => 'jpeg', + 'image/png' => 'png', + 'image/bmp' => 'bmp', + 'image/gif' => 'gif', + ]; + + // get image embed name + if (isset($this->tempDocumentNewImages[$imgPath])) { + $imgName = $this->tempDocumentNewImages[$imgPath]; + } else { + // transform extension + if (isset($extTransform[$imageMimeType])) { + $imgExt = $extTransform[$imageMimeType]; + } else { + throw new Exception("Unsupported image type $imageMimeType"); + } + + // add image to document + $imgName = 'image_' . $rid . '_' . pathinfo($partFileName, PATHINFO_FILENAME) . '.' . $imgExt; + $this->zipClass->pclzipAddFile($imgPath, 'word/media/' . $imgName); + $this->tempDocumentNewImages[$imgPath] = $imgName; + + // setup type for image + $xmlImageType = str_replace(['{IMG}', '{EXT}'], [$imgName, $imgExt], $typeTpl); + $this->tempDocumentContentTypes = str_replace('', $xmlImageType, $this->tempDocumentContentTypes) . ''; + } + + $xmlImageRelation = str_replace(['{RID}', '{IMG}'], [$rid, $imgName], $relationTpl); + + if (!isset($this->tempDocumentRelations[$partFileName])) { + // create new relations file + $this->tempDocumentRelations[$partFileName] = $newRelationsTpl; + // and add it to content types + $xmlRelationsType = str_replace('{RELS}', $this->getRelationsName($partFileName), $newRelationsTypeTpl); + $this->tempDocumentContentTypes = str_replace('', $xmlRelationsType, $this->tempDocumentContentTypes) . ''; + } + + // add image to relations + $this->tempDocumentRelations[$partFileName] = str_replace('', $xmlImageRelation, $this->tempDocumentRelations[$partFileName]) . ''; } /** * @param mixed $search - * @param mixed $replace - * @param integer $limit - * @return void + * @param mixed $replace Path to image, or array("path" => xx, "width" => yy, "height" => zz) + * @param int $limit */ - public function setValue($search, $replace, $limit = -1) + public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT): void { - foreach ($this->temporaryDocumentHeaders as $index => $headerXML) { - $this->temporaryDocumentHeaders[$index] = $this->setValueForPart($this->temporaryDocumentHeaders[$index], $search, $replace, $limit); + // prepare $search_replace + if (!is_array($search)) { + $search = [$search]; } - $this->temporaryDocumentMainPart = $this->setValueForPart($this->temporaryDocumentMainPart, $search, $replace, $limit); + $replacesList = []; + if (!is_array($replace) || isset($replace['path'])) { + $replacesList[] = $replace; + } else { + $replacesList = array_values($replace); + } + + $searchReplace = []; + foreach ($search as $searchIdx => $searchString) { + $searchReplace[$searchString] = $replacesList[$searchIdx] ?? $replacesList[0]; + } - foreach ($this->temporaryDocumentFooters as $index => $headerXML) { - $this->temporaryDocumentFooters[$index] = $this->setValueForPart($this->temporaryDocumentFooters[$index], $search, $replace, $limit); + // collect document parts + $searchParts = [ + $this->getMainPartName() => &$this->tempDocumentMainPart, + ]; + foreach (array_keys($this->tempDocumentHeaders) as $headerIndex) { + $searchParts[$this->getHeaderName($headerIndex)] = &$this->tempDocumentHeaders[$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 = ''; + + $i = 0; + foreach ($searchParts as $partFileName => &$partContent) { + $partVariables = $this->getVariablesForPart($partContent); + + foreach ($searchReplace as $searchString => $replaceImage) { + $varsToReplace = array_filter($partVariables, function ($partVar) use ($searchString) { + return ($partVar == $searchString) || preg_match('/^' . preg_quote($searchString, '/') . ':/', $partVar); + }); + + foreach ($varsToReplace as $varNameWithArgs) { + $varInlineArgs = $this->getImageArgs($varNameWithArgs); + $preparedImageAttrs = $this->prepareImageAttrs($replaceImage, $varInlineArgs); + $imgPath = $preparedImageAttrs['src']; + + // get image index + $imgIndex = $this->getNextRelationsIndex($partFileName); + $rid = 'rId' . $imgIndex; + + // replace preparations + $this->addImageToRelations($partFileName, $rid, $imgPath, $preparedImageAttrs['mime']); + $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl); + + // replace variable + $varNameWithArgsFixed = static::ensureMacroCompleted($varNameWithArgs); + $matches = []; + if (preg_match('/(<[^<]+>)([^<]*)(' . preg_quote($varNameWithArgsFixed, '/') . ')([^>]*)(<[^>]+>)/Uu', $partContent, $matches)) { + $wholeTag = $matches[0]; + array_shift($matches); + [$openTag, $prefix, , $postfix, $closeTag] = $matches; + $replaceXml = $openTag . $prefix . $closeTag . $xmlImage . $openTag . $postfix . $closeTag; + // replace on each iteration, because in one tag we can have 2+ inline variables => before proceed next variable we need to change $partContent + $partContent = $this->setValueForPart($wholeTag, $replaceXml, $partContent, $limit); + } + + if (++$i >= $limit) { + break; + } + } + } } } /** - * Returns array of all variables in template. + * Returns count of all variables in template. * - * @return string[] + * @return array */ - public function getVariables() + public function getVariableCount() { - $variables = $this->getVariablesForPart($this->temporaryDocumentMainPart); + $variables = $this->getVariablesForPart($this->tempDocumentMainPart); - foreach ($this->temporaryDocumentHeaders as $headerXML) { - $variables = array_merge($variables, $this->getVariablesForPart($headerXML)); + foreach ($this->tempDocumentHeaders as $headerXML) { + $variables = array_merge( + $variables, + $this->getVariablesForPart($headerXML) + ); } - foreach ($this->temporaryDocumentFooters as $footerXML) { - $variables = array_merge($variables, $this->getVariablesForPart($footerXML)); + foreach ($this->tempDocumentFooters as $footerXML) { + $variables = array_merge( + $variables, + $this->getVariablesForPart($footerXML) + ); } - return array_unique($variables); + return array_count_values($variables); + } + + /** + * Returns array of all variables in template. + * + * @return string[] + */ + public function getVariables() + { + return array_keys($this->getVariableCount()); } /** * Clone a table row in a template document. * * @param string $search - * @param integer $numberOfClones - * @return void - * @throws \PhpOffice\PhpWord\Exception\Exception + * @param int $numberOfClones */ - public function cloneRow($search, $numberOfClones) + public function cloneRow($search, $numberOfClones): void { - if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') { - $search = '${' . $search . '}'; - } + $search = static::ensureMacroCompleted($search); - $tagPos = strpos($this->temporaryDocumentMainPart, $search); + $tagPos = strpos($this->tempDocumentMainPart, $search); if (!$tagPos) { - throw new Exception("Can not clone row, template variable not found or variable contains markup."); + throw new Exception('Can not clone row, template variable not found or variable contains markup.'); } $rowStart = $this->findRowStart($tagPos); @@ -204,7 +788,8 @@ public function cloneRow($search, $numberOfClones) // If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row. $tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd); if (!preg_match('##', $tmpXmlRow) && - !preg_match('##', $tmpXmlRow)) { + !preg_match('##', $tmpXmlRow) + ) { break; } // This row was a spanned row, update $rowEnd and search for the next row. @@ -214,43 +799,137 @@ public function cloneRow($search, $numberOfClones) } $result = $this->getSlice(0, $rowStart); - for ($i = 1; $i <= $numberOfClones; $i++) { - $result .= preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $i . '}', $xmlRow); - } + $result .= implode('', $this->indexClonedVariables($numberOfClones, $xmlRow)); $result .= $this->getSlice($rowEnd); - $this->temporaryDocumentMainPart = $result; + $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, '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('##', $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('##', $tmpXmlRow) && + !preg_match('##', $tmpXmlRow) + ) { + break; + } + + $tableStart = $this->findTableStart($extraRowEnd + 1); + $tableEnd = $this->findTableEnd($extraRowEnd + 1); + $xmlTable = $this->getSlice($tableStart, $tableEnd); + if (substr_count($xmlTable, '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. + * + * @param string $search + * @param array $values + */ + public function cloneRowAndSetValues($search, $values): void + { + $this->cloneRow($search, count($values)); + + foreach ($values as $rowKey => $rowData) { + $rowNumber = $rowKey + 1; + foreach ($rowData as $macro => $replace) { + $this->setValue($macro . '#' . $rowNumber, $replace); + } + } } /** * Clone a block. * * @param string $blockname - * @param integer $clones - * @param boolean $replace - * @return string|null + * @param int $clones How many time the block should be cloned + * @param bool $replace + * @param bool $indexVariables If true, any variables inside the block will be indexed (postfixed with #1, #2, ...) + * @param array $variableReplacements Array containing replacements for macros found inside the block to clone + * + * @return null|string */ - public function cloneBlock($blockname, $clones = 1, $replace = true) + public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVariables = false, $variableReplacements = null) { $xmlBlock = null; + $matches = []; + $escapedMacroOpeningChars = self::$macroOpeningChars; + $escapedMacroClosingChars = self::$macroClosingChars; preg_match( - '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', - $this->temporaryDocumentMainPart, + //'/(.*((?s)))(.*)((?s))/is', + '/(.*((?s)))(.*)((?s))/is', + //'/(.*((?s)))(.*)((?s))/is', + $this->tempDocumentMainPart, $matches ); if (isset($matches[3])) { $xmlBlock = $matches[3]; - $cloned = array(); - for ($i = 1; $i <= $clones; $i++) { - $cloned[] = $xmlBlock; + if ($indexVariables) { + $cloned = $this->indexClonedVariables($clones, $xmlBlock); + } elseif ($variableReplacements !== null && is_array($variableReplacements)) { + $cloned = $this->replaceClonedVariables($variableReplacements, $xmlBlock); + } else { + $cloned = []; + for ($i = 1; $i <= $clones; ++$i) { + $cloned[] = $xmlBlock; + } } if ($replace) { - $this->temporaryDocumentMainPart = str_replace( + $this->tempDocumentMainPart = str_replace( $matches[2] . $matches[3] . $matches[4], implode('', $cloned), - $this->temporaryDocumentMainPart + $this->tempDocumentMainPart ); } } @@ -263,21 +942,23 @@ public function cloneBlock($blockname, $clones = 1, $replace = true) * * @param string $blockname * @param string $replacement - * @return void */ - public function replaceBlock($blockname, $replacement) + public function replaceBlock($blockname, $replacement): void { + $matches = []; + $escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars); + $escapedMacroClosingChars = preg_quote(self::$macroClosingChars); preg_match( - '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', - $this->temporaryDocumentMainPart, + '/(<\?xml.*)(' . $escapedMacroOpeningChars . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)(.*)()/is', + $this->tempDocumentMainPart, $matches ); if (isset($matches[3])) { - $this->temporaryDocumentMainPart = str_replace( + $this->tempDocumentMainPart = str_replace( $matches[2] . $matches[3] . $matches[4], $replacement, - $this->temporaryDocumentMainPart + $this->tempDocumentMainPart ); } } @@ -286,37 +967,67 @@ public function replaceBlock($blockname, $replacement) * Delete a block of text. * * @param string $blockname - * @return void */ - public function deleteBlock($blockname) + public function deleteBlock($blockname): void { $this->replaceBlock($blockname, ''); } + /** + * Automatically Recalculate Fields on Open. + * + * @param bool $update + */ + public function setUpdateFields($update = true): void + { + $string = $update ? 'true' : 'false'; + $matches = []; + if (preg_match('//', $this->tempDocumentSettingsPart, $matches)) { + $this->tempDocumentSettingsPart = str_replace($matches[0], '', $this->tempDocumentSettingsPart); + } else { + $this->tempDocumentSettingsPart = str_replace('', '', $this->tempDocumentSettingsPart); + } + } + /** * Saves the result document. * * @return string - * @throws \PhpOffice\PhpWord\Exception\Exception */ public function save() { - foreach ($this->temporaryDocumentHeaders as $index => $headerXML) { - $this->zipClass->addFromString($this->getHeaderName($index), $this->temporaryDocumentHeaders[$index]); + foreach ($this->tempDocumentHeaders as $index => $xml) { + $this->savePartWithRels($this->getHeaderName($index), $xml); } - $this->zipClass->addFromString('word/document.xml', $this->temporaryDocumentMainPart); + $this->savePartWithRels($this->getMainPartName(), $this->tempDocumentMainPart); + $this->savePartWithRels($this->getSettingsPartName(), $this->tempDocumentSettingsPart); - foreach ($this->temporaryDocumentFooters as $index => $headerXML) { - $this->zipClass->addFromString($this->getFooterName($index), $this->temporaryDocumentFooters[$index]); + foreach ($this->tempDocumentFooters as $index => $xml) { + $this->savePartWithRels($this->getFooterName($index), $xml); } + $this->zipClass->addFromString($this->getDocumentContentTypesName(), $this->tempDocumentContentTypes); + // Close zip file if (false === $this->zipClass->close()) { - throw new Exception('Could not close zip file.'); + throw new Exception('Could not close zip file.'); // @codeCoverageIgnore } - return $this->temporaryDocumentFilename; + return $this->tempDocumentFilename; + } + + /** + * @param string $fileName + * @param string $xml + */ + protected function savePartWithRels($fileName, $xml): void + { + $this->zipClass->addFromString($fileName, $xml); + if (isset($this->tempDocumentRelations[$fileName])) { + $relsFileName = $this->getRelationsName($fileName); + $this->zipClass->addFromString($relsFileName, $this->tempDocumentRelations[$fileName]); + } } /** @@ -325,9 +1036,8 @@ public function save() * @since 0.8.0 * * @param string $fileName - * @return void */ - public function saveAs($fileName) + public function saveAs($fileName): void { $tempFileName = $this->save(); @@ -335,89 +1045,209 @@ public function saveAs($fileName) unlink($fileName); } - rename($tempFileName, $fileName); + /* + * Note: we do not use `rename` function here, because it loses file ownership data on Windows platform. + * As a result, user cannot open the file directly getting "Access denied" message. + * + * @see https://github.com/PHPOffice/PHPWord/issues/532 + */ + copy($tempFileName, $fileName); + unlink($tempFileName); } /** - * Find and replace placeholders in the given XML section. + * Finds parts of broken macros and sticks them together. + * Macros, while being edited, could be implicitly broken by some of the word processors. + * + * @param string $documentPart The document part in XML representation * - * @param string $documentPartXML - * @param string $search - * @param string $replace - * @param integer $limit * @return string */ - protected function setValueForPart($documentPartXML, $search, $replace, $limit) + protected function fixBrokenMacros($documentPart) { - $pattern = '|\$\{([^\}]+)\}|U'; - preg_match_all($pattern, $documentPartXML, $matches); - foreach ($matches[0] as $value) { - $valueCleaned = preg_replace('/<[^>]+>/', '', $value); - $valueCleaned = preg_replace('/<\/[^>]+>/', '', $valueCleaned); - $documentPartXML = str_replace($value, $valueCleaned, $documentPartXML); - } + $brokenMacroOpeningChars = substr(self::$macroOpeningChars, 0, 1); + $endMacroOpeningChars = substr(self::$macroOpeningChars, 1); + $macroClosingChars = self::$macroClosingChars; - if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') { - $search = '${' . $search . '}'; - } + return preg_replace_callback( + '/\\' . $brokenMacroOpeningChars . '(?:\\' . $endMacroOpeningChars . '|[^{$]*\>\{)[^' . $macroClosingChars . '$]*\}/U', + function ($match) { + return strip_tags($match[0]); + }, + $documentPart + ); + } - if (!String::isUTF8($replace)) { - $replace = utf8_encode($replace); + /** + * Find and replace macros in the given XML section. + * + * @param array|string $search + * @param array|string $replace + * @param array|string $documentPartXML + * @param int $limit + * + * @return ($documentPartXML is string ? string : array) + */ + protected function setValueForPart($search, $replace, $documentPartXML, $limit) + { + // Note: we can't use the same function for both cases here, because of performance considerations. + if (self::MAXIMUM_REPLACEMENTS_DEFAULT === $limit) { + return str_replace($search, $replace, $documentPartXML); } + $regExpEscaper = new RegExp(); - $regExpDelim = '/'; - $escapedSearch = preg_quote($search, $regExpDelim); - return preg_replace("{$regExpDelim}{$escapedSearch}{$regExpDelim}u", $replace, $documentPartXML, $limit); + return preg_replace($regExpEscaper->escape($search), $replace, $documentPartXML, $limit); } /** * Find all variables in $documentPartXML. * * @param string $documentPartXML + * * @return string[] */ protected function getVariablesForPart($documentPartXML) { - preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches); + $matches = []; + $escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars); + $escapedMacroClosingChars = preg_quote(self::$macroClosingChars); + + preg_match_all("/$escapedMacroOpeningChars(.*?)$escapedMacroClosingChars/i", $documentPartXML, $matches); return $matches[1]; } + /** + * Get the name of the header file for $index. + * + * @param int $index + * + * @return string + */ + protected function getHeaderName($index) + { + return sprintf('word/header%d.xml', $index); + } + + /** + * Usually, the name of main part document will be 'document.xml'. However, some .docx files (possibly those from Office 365, experienced also on documents from Word Online created from blank templates) have file 'document22.xml' in their zip archive instead of 'document.xml'. This method searches content types file to correctly determine the file name. + * + * @return string + */ + protected function getMainPartName() + { + $contentTypes = $this->zipClass->getFromName('[Content_Types].xml'); + + $pattern = '~PartName="\/(word\/document.*?\.xml)" ContentType="application\/vnd\.openxmlformats-officedocument\.wordprocessingml\.document\.main\+xml"~'; + + $matches = []; + preg_match($pattern, $contentTypes, $matches); + + return array_key_exists(1, $matches) ? $matches[1] : 'word/document.xml'; + } + + /** + * The name of the file containing the Settings part. + * + * @return string + */ + protected function getSettingsPartName() + { + return 'word/settings.xml'; + } + /** * Get the name of the footer file for $index. * - * @param integer $index + * @param int $index + * * @return string */ - private function getFooterName($index) + protected function getFooterName($index) { return sprintf('word/footer%d.xml', $index); } /** - * Get the name of the header file for $index. + * Get the name of the relations file for document part. + * + * @param string $documentPartName * - * @param integer $index * @return string */ - private function getHeaderName($index) + protected function getRelationsName($documentPartName) { - return sprintf('word/header%d.xml', $index); + return 'word/_rels/' . pathinfo($documentPartName, PATHINFO_BASENAME) . '.rels'; + } + + protected function getNextRelationsIndex($documentPartName) + { + if (isset($this->tempDocumentRelations[$documentPartName])) { + $candidate = substr_count($this->tempDocumentRelations[$documentPartName], 'tempDocumentRelations[$documentPartName], 'Id="rId' . $candidate . '"') !== false) { + ++$candidate; + } + + return $candidate; + } + + return 1; + } + + /** + * @return string + */ + 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, + 'tempDocumentMainPart) - $offset) * -1) + ); + + if (!$rowStart) { + $rowStart = strrpos( + $this->tempDocumentMainPart, + '', + ((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, '', $offset) + 7; } /** * Find the start position of the nearest table row before $offset. * - * @param integer $offset - * @return integer - * @throws \PhpOffice\PhpWord\Exception\Exception + * @param int $offset + * + * @return int */ - private function findRowStart($offset) + protected function findRowStart($offset) { - $rowStart = strrpos($this->temporaryDocumentMainPart, 'temporaryDocumentMainPart) - $offset) * -1)); + $rowStart = strrpos($this->tempDocumentMainPart, 'tempDocumentMainPart) - $offset) * -1)); if (!$rowStart) { - $rowStart = strrpos($this->temporaryDocumentMainPart, '', ((strlen($this->temporaryDocumentMainPart) - $offset) * -1)); + $rowStart = strrpos($this->tempDocumentMainPart, '', ((strlen($this->tempDocumentMainPart) - $offset) * -1)); } if (!$rowStart) { throw new Exception('Can not find the start position of the row to clone.'); @@ -429,27 +1259,249 @@ private function findRowStart($offset) /** * Find the end position of the nearest table row after $offset. * - * @param integer $offset - * @return integer + * @param int $offset + * + * @return int */ - private function findRowEnd($offset) + protected function findRowEnd($offset) { - return strpos($this->temporaryDocumentMainPart, '', $offset) + 7; + return strpos($this->tempDocumentMainPart, '', $offset) + 7; } /** * Get a slice of a string. * - * @param integer $startPosition - * @param integer $endPosition + * @param int $startPosition + * @param int $endPosition + * * @return string */ - private function getSlice($startPosition, $endPosition = 0) + protected function getSlice($startPosition, $endPosition = 0) { if (!$endPosition) { - $endPosition = strlen($this->temporaryDocumentMainPart); + $endPosition = strlen($this->tempDocumentMainPart); + } + + return substr($this->tempDocumentMainPart, $startPosition, ($endPosition - $startPosition)); + } + + /** + * Replaces variable names in cloned + * rows/blocks with indexed names. + * + * @param int $count + * @param string $xmlBlock + * + * @return array + */ + 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("/$escapedMacroOpeningChars([^:]*?)(:.*?)?$escapedMacroClosingChars/", self::$macroOpeningChars . '\1#' . $i . '\2' . self::$macroClosingChars, $xmlBlock); + } + + return $results; + } + + /** + * Replace carriage returns with xml. + */ + public function replaceCarriageReturns(string $string): string + { + return str_replace(["\r\n", "\r", "\n"], '', $string); + } + + /** + * Replaces variables with values from array, array keys are the variable names. + * + * @param array $variableReplacements + * @param string $xmlBlock + * + * @return string[] + */ + protected function replaceClonedVariables($variableReplacements, $xmlBlock) + { + $results = []; + foreach ($variableReplacements as $replacementArray) { + $localXmlBlock = $xmlBlock; + foreach ($replacementArray as $search => $replacement) { + $localXmlBlock = $this->setValueForPart(self::ensureMacroCompleted($search), $replacement, $localXmlBlock, self::MAXIMUM_REPLACEMENTS_DEFAULT); + } + $results[] = $localXmlBlock; } - return substr($this->temporaryDocumentMainPart, $startPosition, ($endPosition - $startPosition)); + return $results; + } + + /** + * Replace an XML block surrounding a macro with a new block. + * + * @param string $macro Name of macro + * @param string $block New block content + * @param string $blockType XML tag type of block + * + * @return TemplateProcessor Fluent interface + */ + public function replaceXmlBlock($macro, $block, $blockType = 'w:p') + { + $where = $this->findContainingXmlBlockForMacro($macro, $blockType); + if (is_array($where)) { + $this->tempDocumentMainPart = $this->getSlice(0, $where['start']) . $block . $this->getSlice($where['end']); + } + + return $this; + } + + /** + * Find start and end of XML block containing the given macro + * e.g. ...${macro}.... + * + * Note that only the first instance of the macro will be found + * + * @param string $macro Name of macro + * @param string $blockType XML tag for block + * + * @return bool|int[] FALSE if not found, otherwise array with start and end + */ + protected function findContainingXmlBlockForMacro($macro, $blockType = 'w:p') + { + $macroPos = $this->findMacro($macro); + if (0 > $macroPos) { + return false; + } + $start = $this->findXmlBlockStart($macroPos, $blockType); + if (0 > $start) { + return false; + } + $end = $this->findXmlBlockEnd($start, $blockType); + //if not found or if resulting string does not contain the macro we are searching for + if (0 > $end || strstr($this->getSlice($start, $end), $macro) === false) { + return false; + } + + return ['start' => $start, 'end' => $end]; + } + + /** + * Find the position of (the start of) a macro. + * + * Returns -1 if not found, otherwise position of opening $ + * + * Note that only the first instance of the macro will be found + * + * @param string $search Macro name + * @param int $offset Offset from which to start searching + * + * @return int -1 if macro not found + */ + protected function findMacro($search, $offset = 0) + { + $search = static::ensureMacroCompleted($search); + $pos = strpos($this->tempDocumentMainPart, $search, $offset); + + return ($pos === false) ? -1 : $pos; + } + + /** + * Find the start position of the nearest XML block start before $offset. + * + * @param int $offset Search position + * @param string $blockType XML Block tag + * + * @return int -1 if block start not found + */ + protected function findXmlBlockStart($offset, $blockType) + { + $reverseOffset = (strlen($this->tempDocumentMainPart) - $offset) * -1; + // first try XML tag with attributes + $blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . ' ', $reverseOffset); + // if not found, or if found but contains the XML tag without attribute + if (false === $blockStart || strrpos($this->getSlice($blockStart, $offset), '<' . $blockType . '>')) { + // also try XML tag without attributes + $blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . '>', $reverseOffset); + } + + return ($blockStart === false) ? -1 : $blockStart; + } + + /** + * Find the nearest block end position after $offset. + * + * @param int $offset Search position + * @param string $blockType XML Block tag + * + * @return int -1 if block end not found + */ + protected function findXmlBlockEnd($offset, $blockType) + { + $blockEndStart = strpos($this->tempDocumentMainPart, '', $offset); + // return position of end of tag if found, otherwise -1 + + return ($blockEndStart === false) ? -1 : $blockEndStart + 3 + strlen($blockType); + } + + /** + * Splits a w:r/w:t into a list of w:r where each ${macro} is in a separate w:r. + * + * @param string $text + * + * @return string + */ + protected function splitTextIntoTexts($text) + { + if (!$this->textNeedsSplitting($text)) { + return $text; + } + $matches = []; + if (preg_match('/()/i', $text, $matches)) { + $extractedStyle = $matches[0]; + } else { + $extractedStyle = ''; + } + + $unformattedText = preg_replace('/>\s+<', $text); + $result = str_replace([self::$macroOpeningChars, self::$macroClosingChars], ['' . $extractedStyle . '' . self::$macroOpeningChars, self::$macroClosingChars . '' . $extractedStyle . ''], $unformattedText); + + return str_replace(['' . $extractedStyle . '', '', ''], ['', '', ''], $result); + } + + /** + * Returns true if string contains a macro that is not in it's own w:r. + * + * @param string $text + * + * @return bool + */ + protected function textNeedsSplitting($text) + { + $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 599d8a6fdd..13859d8227 100644 --- a/src/PhpWord/Writer/AbstractWriter.php +++ b/src/PhpWord/Writer/AbstractWriter.php @@ -1,4 +1,5 @@ '', 'object' => ''); + protected $mediaPaths = ['image' => '', 'object' => '']; /** - * Use disk caching + * Use disk caching. * * @var bool */ private $useDiskCaching = false; /** - * Disk caching directory + * Disk caching directory. * * @var string */ private $diskCachingDirectory = './'; /** - * Temporary directory + * Temporary directory. * * @var string */ private $tempDir = ''; /** - * Original file name + * Original file name. * * @var string */ private $originalFilename; /** - * Temporary file name + * Temporary file name. * * @var string */ private $tempFilename; /** - * Get PhpWord object + * Get PhpWord object. * - * @return \PhpOffice\PhpWord\PhpWord - * @throws \PhpOffice\PhpWord\Exception\Exception + * @return PhpWord */ public function getPhpWord() { - if (!is_null($this->phpWord)) { + if (null !== $this->phpWord) { return $this->phpWord; - } else { - throw new Exception("No PhpWord assigned."); } + + throw new Exception('No PhpWord assigned.'); } /** - * Set PhpWord object + * Set PhpWord object. * - * @param \PhpOffice\PhpWord\PhpWord * @return self */ - public function setPhpWord(PhpWord $phpWord = null) + public function setPhpWord(?PhpWord $phpWord = null) { $this->phpWord = $phpWord; + return $this; } /** - * Get writer part + * Get writer part. * * @param string $partName Writer part name + * * @return mixed */ public function getWriterPart($partName = '') { if ($partName != '' && isset($this->writerParts[strtolower($partName)])) { return $this->writerParts[strtolower($partName)]; - } else { - return null; } + + return null; } /** - * Get use disk caching status + * Get use disk caching status. * * @return bool */ @@ -146,18 +147,18 @@ public function isUseDiskCaching() } /** - * Set use disk caching status + * Set use disk caching status. * * @param bool $value * @param string $directory + * * @return self - * @throws \PhpOffice\PhpWord\Exception\Exception */ public function setUseDiskCaching($value = false, $directory = null) { $this->useDiskCaching = $value; - if (!is_null($directory)) { + if (null !== $directory) { if (is_dir($directory)) { $this->diskCachingDirectory = $directory; } else { @@ -169,7 +170,7 @@ public function setUseDiskCaching($value = false, $directory = null) } /** - * Get disk caching directory + * Get disk caching directory. * * @return string */ @@ -179,7 +180,7 @@ public function getDiskCachingDirectory() } /** - * Get temporary directory + * Get temporary directory. * * @return string */ @@ -189,9 +190,10 @@ public function getTempDir() } /** - * Set temporary directory + * Set temporary directory. * * @param string $value + * * @return self */ public function setTempDir($value) @@ -205,25 +207,26 @@ public function setTempDir($value) } /** - * Get temporary file name + * Get temporary file name. * * If $filename is php://output or php://stdout, make it a temporary file * * @param string $filename + * * @return string */ protected function getTempFile($filename) { // Temporary directory - $this->setTempDir(Settings::getTempDir() . '/PHPWordWriter/'); + $this->setTempDir(Settings::getTempDir() . uniqid('/PHPWordWriter_', true) . '/'); // Temporary file $this->originalFilename = $filename; - if (strtolower($filename) == 'php://output' || strtolower($filename) == 'php://stdout') { + if (strpos(strtolower($filename), 'php://') === 0) { $filename = tempnam(Settings::getTempDir(), 'PhpWord'); if (false === $filename) { - $filename = $this->originalFilename; - } + $filename = $this->originalFilename; // @codeCoverageIgnore + } // @codeCoverageIgnore } $this->tempFilename = $filename; @@ -232,11 +235,8 @@ protected function getTempFile($filename) /** * Cleanup temporary file. - * - * @return void - * @throws \PhpOffice\PhpWord\Exception\CopyFileException */ - protected function cleanupTempFile() + protected function cleanupTempFile(): void { if ($this->originalFilename != $this->tempFilename) { // @codeCoverageIgnoreStart @@ -253,10 +253,8 @@ protected function cleanupTempFile() /** * Clear temporary directory. - * - * @return void */ - protected function clearTempDir() + protected function clearTempDir(): void { if (is_dir($this->tempDir)) { $this->deleteDir($this->tempDir); @@ -264,11 +262,11 @@ protected function clearTempDir() } /** - * Get ZipArchive object + * Get ZipArchive object. * * @param string $filename - * @return \PhpOffice\PhpWord\Shared\ZipArchive - * @throws \Exception + * + * @return ZipArchive */ protected function getZipArchive($filename) { @@ -293,17 +291,18 @@ protected function getZipArchive($filename) } /** - * Open file for writing + * Open file for writing. + * + * @since 0.11.0 * * @param string $filename + * * @return resource - * @throws \Exception - * @since 0.11.0 */ protected function openFile($filename) { $filename = $this->getTempFile($filename); - $fileHandle = fopen($filename, 'w'); + $fileHandle = fopen($filename, 'wb'); // @codeCoverageIgnoreStart // Can't find any test case. Uncomment when found. if ($fileHandle === false) { @@ -321,9 +320,8 @@ protected function openFile($filename) * * @param resource $fileHandle * @param string $content - * @return void */ - protected function writeFile($fileHandle, $content) + protected function writeFile($fileHandle, $content): void { fwrite($fileHandle, $content); fclose($fileHandle); @@ -333,11 +331,9 @@ protected function writeFile($fileHandle, $content) /** * Add files to package. * - * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip * @param mixed $elements - * @return void */ - protected function addFilesToPackage(ZipArchive $zip, $elements) + protected function addFilesToPackage(ZipArchive $zip, $elements): void { foreach ($elements as $element) { $type = $element['type']; // image|object|link @@ -350,13 +346,8 @@ protected function addFilesToPackage(ZipArchive $zip, $elements) // Retrive GD image content or get local media if (isset($element['isMemImage']) && $element['isMemImage']) { - $image = call_user_func($element['createFunction'], $element['source']); - 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); } @@ -368,20 +359,19 @@ protected function addFilesToPackage(ZipArchive $zip, $elements) * * Get the actual source from an archive image. * - * @param \PhpOffice\PhpWord\Shared\ZipArchive $zipPackage + * @param ZipArchive $zipPackage * @param string $source * @param string $target - * @return void */ - protected function addFileToPackage($zipPackage, $source, $target) + protected function addFileToPackage($zipPackage, $source, $target): void { $isArchive = strpos($source, 'zip://') !== false; $actualSource = null; if ($isArchive) { $source = substr($source, 6); - list($zipFilename, $imageFilename) = explode('#', $source); + [$zipFilename, $imageFilename] = explode('#', $source); - $zip = new ZipArchive; + $zip = new ZipArchive(); if ($zip->open($zipFilename) !== false) { if ($zip->locateName($imageFilename)) { $zip->extractTo($this->getTempDir(), $imageFilename); @@ -393,7 +383,7 @@ protected function addFileToPackage($zipPackage, $source, $target) $actualSource = $source; } - if (!is_null($actualSource)) { + if (null !== $actualSource) { $zipPackage->addFile($actualSource, $target); } } @@ -402,31 +392,19 @@ protected function addFileToPackage($zipPackage, $source, $target) * Delete directory. * * @param string $dir - * @return void */ - private function deleteDir($dir) + private function deleteDir($dir): void { foreach (scandir($dir) as $file) { if ($file === '.' || $file === '..') { continue; - } elseif (is_file($dir . "/" . $file)) { - unlink($dir . "/" . $file); - } elseif (is_dir($dir . "/" . $file)) { - $this->deleteDir($dir . "/" . $file); + } elseif (is_file($dir . '/' . $file)) { + unlink($dir . '/' . $file); + } elseif (is_dir($dir . '/' . $file)) { + $this->deleteDir($dir . '/' . $file); } } rmdir($dir); } - - /** - * Get use disk caching status - * - * @deprecated 0.10.0 - * @codeCoverageIgnore - */ - public function getUseDiskCaching() - { - return $this->isUseDiskCaching(); - } } diff --git a/src/PhpWord/Writer/EPub3.php b/src/PhpWord/Writer/EPub3.php new file mode 100644 index 0000000000..b2ed9700d1 --- /dev/null +++ b/src/PhpWord/Writer/EPub3.php @@ -0,0 +1,91 @@ +setPhpWord($phpWord); + + // Create parts + $this->parts = [ + 'Mimetype' => 'mimetype', + 'Content' => 'content.opf', + 'Toc' => 'toc.ncx', + 'Styles' => 'styles.css', + 'Manifest' => 'META-INF/container.xml', + 'Nav' => 'nav.xhtml', + 'ContentXhtml' => 'content.xhtml', + ]; + foreach (array_keys($this->parts) as $partName) { + $partClass = static::class . '\\Part\\' . $partName; + if (class_exists($partClass)) { + /** @var WriterPartInterface $part */ + $part = new $partClass($partName === 'Content' || $partName === 'ContentXhtml' ? $phpWord : null); + $part->setParentWriter($this); + $this->writerParts[strtolower($partName)] = $part; + } + } + + // Set package paths + $this->mediaPaths = ['image' => 'Images/', 'object' => 'Objects/']; + } + + /** + * Save PhpWord to file. + */ + public function save(string $filename): void + { + $filename = $this->getTempFile($filename); + $zip = $this->getZipArchive($filename); + + // Add mimetype first without compression + $zip->addFromString('mimetype', 'application/epub+zip'); + $zip->addEmptyDir('META-INF'); + + // Add other files + foreach ($this->parts as $partName => $fileName) { + if ($fileName === '') { + continue; + } + $part = $this->getWriterPart($partName); + if (!$part instanceof AbstractPart) { + continue; + } + $zip->addFromString($fileName, $part->write()); + } + + // Close zip archive + $zip->close(); + + // Cleanup temp file + $this->cleanupTempFile(); + } +} diff --git a/src/PhpWord/Writer/EPub3/Element/AbstractElement.php b/src/PhpWord/Writer/EPub3/Element/AbstractElement.php new file mode 100644 index 0000000000..c95efec0f6 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Element/AbstractElement.php @@ -0,0 +1,45 @@ +getXmlWriter(); + $xmlWriter->setIndent(false); + $element = $this->getElement(); + if (!$element instanceof ImageElement) { + return; + } + $mediaIndex = $element->getMediaIndex(); + $target = 'media/image' . $mediaIndex . '.' . $element->getImageExtension(); + if (!$this->withoutP) { + $xmlWriter->startElement('p'); + } + $xmlWriter->startElement('img'); + $xmlWriter->writeAttribute('src', $target); + $style = ''; + if ($element->getStyle()->getWidth() !== null) { + $style .= 'width:' . $element->getStyle()->getWidth() . 'px;'; + } + if ($element->getStyle()->getHeight() !== null) { + $style .= 'height:' . $element->getStyle()->getHeight() . 'px;'; + } + if ($style !== '') { + $xmlWriter->writeAttribute('style', $style); + } + $xmlWriter->endElement(); // img + if (!$this->withoutP) { + $xmlWriter->endElement(); // p + } + } +} diff --git a/src/PhpWord/Writer/EPub3/Element/Text.php b/src/PhpWord/Writer/EPub3/Element/Text.php new file mode 100644 index 0000000000..2e138c0509 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Element/Text.php @@ -0,0 +1,50 @@ +getXmlWriter(); + $xmlWriter->setIndent(true); + $xmlWriter->setIndentString(' '); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Text) { + return; + } + + $fontStyle = $element->getFontStyle(); + $paragraphStyle = $element->getParagraphStyle(); + + if (!$this->withoutP) { + $xmlWriter->startElement('p'); + if (is_string($paragraphStyle) && $paragraphStyle !== '') { + $xmlWriter->writeAttribute('class', $paragraphStyle); + } + } + + if (!empty($fontStyle)) { + $xmlWriter->startElement('span'); + if (is_string($fontStyle)) { + $xmlWriter->writeAttribute('class', $fontStyle); + } + } + + $xmlWriter->text($element->getText()); + + if (!empty($fontStyle)) { + $xmlWriter->endElement(); // span + } + + if (!$this->withoutP) { + $xmlWriter->endElement(); // p + } + } +} diff --git a/src/PhpWord/Writer/EPub3/Part.php b/src/PhpWord/Writer/EPub3/Part.php new file mode 100644 index 0000000000..25dfa6654d --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Part.php @@ -0,0 +1,45 @@ +parentWriter = $writer; + } + + /** + * Get parent writer. + */ + public function getParentWriter(): AbstractWriter + { + return $this->parentWriter; + } + + /** + * Write part content. + */ + abstract public function write(): string; +} diff --git a/src/PhpWord/Writer/EPub3/Part/Content.php b/src/PhpWord/Writer/EPub3/Part/Content.php new file mode 100644 index 0000000000..217c56cc1f --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Part/Content.php @@ -0,0 +1,130 @@ +phpWord = $phpWord; + } + + /** + * Get XML Writer. + * + * @return XMLWriter + */ + protected function getXmlWriter() + { + $xmlWriter = new XMLWriter(); + $xmlWriter->openMemory(); + $xmlWriter->startDocument('1.0', 'UTF-8'); + + return $xmlWriter; + } + + /** + * Write part content. + */ + public function write(): string + { + if ($this->phpWord === null) { + throw new Exception('No PhpWord assigned.'); + } + + $xmlWriter = $this->getXmlWriter(); + $docInfo = $this->phpWord->getDocInfo(); + + // Write package + $xmlWriter->startElement('package'); + $xmlWriter->writeAttribute('xmlns', 'http://www.idpf.org/2007/opf'); + $xmlWriter->writeAttribute('version', '3.0'); + $xmlWriter->writeAttribute('unique-identifier', 'book-id'); + $xmlWriter->writeAttribute('xml:lang', 'en'); + + // Write metadata + $xmlWriter->startElement('metadata'); + $xmlWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $xmlWriter->writeAttribute('xmlns:opf', 'http://www.idpf.org/2007/opf'); + + // Required elements + $xmlWriter->startElement('dc:identifier'); + $xmlWriter->writeAttribute('id', 'book-id'); + $xmlWriter->text('book-id-' . uniqid()); + $xmlWriter->endElement(); + $xmlWriter->writeElement('dc:title', $docInfo->getTitle() ?: 'Untitled'); + $xmlWriter->writeElement('dc:language', 'en'); + + // Required modified timestamp + $xmlWriter->startElement('meta'); + $xmlWriter->writeAttribute('property', 'dcterms:modified'); + $xmlWriter->text(date('Y-m-d\TH:i:s\Z')); + $xmlWriter->endElement(); + + $xmlWriter->endElement(); // metadata + + // Write manifest + $xmlWriter->startElement('manifest'); + + // Add nav document (required) + $xmlWriter->startElement('item'); + $xmlWriter->writeAttribute('id', 'nav'); + $xmlWriter->writeAttribute('href', 'nav.xhtml'); + $xmlWriter->writeAttribute('media-type', 'application/xhtml+xml'); + $xmlWriter->writeAttribute('properties', 'nav'); + $xmlWriter->endElement(); + + // Add content document + $xmlWriter->startElement('item'); + $xmlWriter->writeAttribute('id', 'content'); + $xmlWriter->writeAttribute('href', 'content.xhtml'); + $xmlWriter->writeAttribute('media-type', 'application/xhtml+xml'); + $xmlWriter->endElement(); + + $xmlWriter->endElement(); // manifest + + // Write spine + $xmlWriter->startElement('spine'); + $xmlWriter->startElement('itemref'); + $xmlWriter->writeAttribute('idref', 'content'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); // spine + + $xmlWriter->endElement(); // package + + return $xmlWriter->outputMemory(true); + } +} diff --git a/src/PhpWord/Writer/EPub3/Part/ContentXhtml.php b/src/PhpWord/Writer/EPub3/Part/ContentXhtml.php new file mode 100644 index 0000000000..74143a30ef --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Part/ContentXhtml.php @@ -0,0 +1,118 @@ +phpWord = $phpWord; + } + + /** + * Get XML Writer. + * + * @return XMLWriter + */ + protected function getXmlWriter() + { + $xmlWriter = new XMLWriter(); + $xmlWriter->openMemory(); + + return $xmlWriter; + } + + /** + * Write part content. + */ + public function write(): string + { + if ($this->phpWord === null) { + throw new \PhpOffice\PhpWord\Exception\Exception('No PhpWord assigned.'); + } + + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8'); + $xmlWriter->startElement('html'); + $xmlWriter->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); + $xmlWriter->writeAttribute('xmlns:epub', 'http://www.idpf.org/2007/ops'); + $xmlWriter->startElement('head'); + $xmlWriter->writeElement('title', $this->phpWord->getDocInfo()->getTitle() ?: 'Untitled'); + $xmlWriter->endElement(); // head + $xmlWriter->startElement('body'); + + // Write sections content + foreach ($this->phpWord->getSections() as $section) { + $xmlWriter->startElement('div'); + $xmlWriter->writeAttribute('class', 'section'); + + foreach ($section->getElements() as $element) { + if ($element instanceof TextRun) { + $xmlWriter->startElement('p'); + $this->writeTextRun($element, $xmlWriter); + $xmlWriter->endElement(); // p + } elseif (method_exists($element, 'getText')) { + $text = $element->getText(); + $xmlWriter->startElement('p'); + if ($text instanceof TextRun) { + $this->writeTextRun($text, $xmlWriter); + } elseif ($text !== null) { + $xmlWriter->text((string) $text); + } + $xmlWriter->endElement(); // p + } + } + + $xmlWriter->endElement(); // div + } + + $xmlWriter->endElement(); // body + $xmlWriter->endElement(); // html + + return $xmlWriter->outputMemory(true); + } + + protected function writeTextElement(AbstractElement $textElement, XMLWriter $xmlWriter): void + { + if ($textElement instanceof Text) { + $text = $textElement->getText(); + if ($text !== null) { + $xmlWriter->text((string) $text); + } + } elseif (method_exists($textElement, 'getText')) { + $text = $textElement->getText(); + if ($text instanceof TextRun) { + $this->writeTextRun($text, $xmlWriter); + } elseif ($text !== null) { + $xmlWriter->text((string) $text); + } + } + } + + protected function writeTextRun(TextRun $textRun, XMLWriter $xmlWriter): void + { + foreach ($textRun->getElements() as $element) { + $this->writeTextElement($element, $xmlWriter); + } + } +} diff --git a/src/PhpWord/Writer/EPub3/Part/Manifest.php b/src/PhpWord/Writer/EPub3/Part/Manifest.php new file mode 100644 index 0000000000..fcb0d1f428 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Part/Manifest.php @@ -0,0 +1,40 @@ +'; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + + return $content; + } +} diff --git a/src/PhpWord/Writer/EPub3/Part/Meta.php b/src/PhpWord/Writer/EPub3/Part/Meta.php new file mode 100644 index 0000000000..4b01097653 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Part/Meta.php @@ -0,0 +1,74 @@ +openMemory(); + $xmlWriter->startDocument('1.0', 'UTF-8'); + + return $xmlWriter; + } + + /** + * Write part content. + */ + public function write(): string + { + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('metadata'); + $xmlWriter->writeAttribute('xmlns', 'http://www.idpf.org/2007/opf'); + $xmlWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + + // Write basic metadata + $title = $this->getParentWriter()->getPhpWord()->getDocInfo()->getTitle() ?: 'Sample EPub3 Document'; + $xmlWriter->writeRaw('' . htmlspecialchars($title, ENT_QUOTES) . ''); + $xmlWriter->writeElement('dc:language', 'en'); + $xmlWriter->writeElement('dc:identifier', 'urn:uuid:12345'); + $xmlWriter->writeAttribute('id', 'bookid'); + + // Write document info if available + $docInfo = $this->getParentWriter()->getPhpWord()->getDocInfo(); + if ($docInfo->getCreator()) { + $xmlWriter->writeElement('dc:creator', $docInfo->getCreator()); + } + + // Write modification date + $xmlWriter->startElement('meta'); + $xmlWriter->writeAttribute('property', 'dcterms:modified'); + $xmlWriter->text('2023-01-01T00:00:00Z'); + $xmlWriter->endElement(); + + $xmlWriter->endElement(); // metadata + + return $xmlWriter->getData(); + } +} diff --git a/src/PhpWord/Writer/EPub3/Part/Mimetype.php b/src/PhpWord/Writer/EPub3/Part/Mimetype.php new file mode 100644 index 0000000000..8e5b7d41ba --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Part/Mimetype.php @@ -0,0 +1,33 @@ +openMemory(); + + return $xmlWriter; + } + + public function write(): string + { + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8'); + $xmlWriter->startElement('html'); + $xmlWriter->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); + $xmlWriter->writeAttribute('xmlns:epub', 'http://www.idpf.org/2007/ops'); + + $xmlWriter->startElement('head'); + $xmlWriter->writeElement('title', 'Navigation'); + $xmlWriter->endElement(); // head + + $xmlWriter->startElement('body'); + $xmlWriter->startElement('nav'); + $xmlWriter->writeAttribute('epub:type', 'toc'); + $xmlWriter->writeAttribute('id', 'toc'); + + // Add navigation items here if needed + $xmlWriter->writeElement('h1', 'Table of Contents'); + $xmlWriter->startElement('ol'); + // Add at least one list item to satisfy EPUB 3.3 requirements + $xmlWriter->startElement('li'); + $xmlWriter->startElement('a'); + $xmlWriter->writeAttribute('href', 'content.xhtml'); + $xmlWriter->text('Content'); + $xmlWriter->endElement(); // a + $xmlWriter->endElement(); // li + $xmlWriter->endElement(); // ol + + $xmlWriter->endElement(); // nav + $xmlWriter->endElement(); // body + $xmlWriter->endElement(); // html + + return $xmlWriter->outputMemory(); + } +} diff --git a/src/PhpWord/Writer/EPub3/Style/AbstractStyle.php b/src/PhpWord/Writer/EPub3/Style/AbstractStyle.php new file mode 100644 index 0000000000..82cd488b62 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Style/AbstractStyle.php @@ -0,0 +1,75 @@ +parentWriter = $writer; + + return $this; + } + + /** + * Set XML Writer. + */ + public function setXmlWriter(XMLWriter $writer): self + { + $this->xmlWriter = $writer; + + return $this; + } + + /** + * Get parent writer. + */ + public function getParentWriter(): AbstractWriter + { + return $this->parentWriter; + } + + /** + * Write style content. + */ + abstract public function write(): string; +} diff --git a/src/PhpWord/Writer/EPub3/Style/Font.php b/src/PhpWord/Writer/EPub3/Style/Font.php new file mode 100644 index 0000000000..4e35eeee41 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Style/Font.php @@ -0,0 +1,39 @@ +setPhpWord($phpWord); - $this->parts = array('Head', 'Body'); + $this->parts = ['Head', 'Body']; foreach ($this->parts as $partName) { - $partClass = 'PhpOffice\\PhpWord\\Writer\\HTML\\Part\\' . $partName; + $partClass = self::class . '\\Part\\' . $partName; if (class_exists($partClass)) { - /** @var \PhpOffice\PhpWord\Writer\HTML\Part\AbstractPart $part Type hint */ + /** @var AbstractPart $part Type hint */ $part = new $partClass(); $part->setParentWriter($this); $this->writerParts[strtolower($partName)] = $part; @@ -62,20 +88,17 @@ public function __construct(PhpWord $phpWord = null) /** * Save PhpWord to file. - * - * @param string $filename - * @return void - * @throws \PhpOffice\PhpWord\Exception\Exception */ - public function save($filename = null) + public function save(string $filename): void { $this->writeFile($this->openFile($filename), $this->getContent()); } /** - * Get content + * Get content. * * @return string + * * @since 0.11.0 */ public function getContent() @@ -84,16 +107,58 @@ public function getContent() $content .= '' . PHP_EOL; $content .= '' . PHP_EOL; - $content .= '' . 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 .= "" . PHP_EOL; $content .= $this->getWriterPart('Head')->write(); $content .= $this->getWriterPart('Body')->write(); $content .= '' . PHP_EOL; + // Trigger a callback for editing the entire HTML + $callback = $this->editCallback; + if ($callback !== null) { + $content = $callback($content); + } + return $content; } /** - * Get is PDF + * 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. * * @return bool */ @@ -103,7 +168,7 @@ public function isPdf() } /** - * Get notes + * Get notes. * * @return array */ @@ -117,22 +182,57 @@ public function getNotes() * * @param int $noteId * @param string $noteMark - * @return void */ - public function addNote($noteId, $noteMark) + public function addNote($noteId, $noteMark): void { $this->notes[$noteId] = $noteMark; } /** - * Write document - * - * @return string - * @deprecated 0.11.0 - * @codeCoverageIgnore + * 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 writeDocument() + public function setDefaultWhiteSpace(string $value): self { - return $this->getContent(); + $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 73f88d3d8d..0e6a112eed 100644 --- a/src/PhpWord/Writer/HTML/Element/AbstractElement.php +++ b/src/PhpWord/Writer/HTML/Element/AbstractElement.php @@ -1,4 +1,5 @@ parentWriter = $parentWriter; $this->element = $element; @@ -71,9 +70,8 @@ public function __construct(AbstractWriter $parentWriter, Element $element, $wit * Set without paragraph. * * @param bool $value - * @return void */ - public function setWithoutP($value) + public function setWithoutP($value): void { $this->withoutP = $value; } diff --git a/src/PhpWord/Writer/HTML/Element/Bookmark.php b/src/PhpWord/Writer/HTML/Element/Bookmark.php new file mode 100644 index 0000000000..55a8c22e27 --- /dev/null +++ b/src/PhpWord/Writer/HTML/Element/Bookmark.php @@ -0,0 +1,46 @@ +element instanceof \PhpOffice\PhpWord\Element\Bookmark) { + return ''; + } + + $content = ''; + $content .= $this->writeOpening(); + $content .= "element->getName()}\"/>"; + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/src/PhpWord/Writer/HTML/Element/Container.php b/src/PhpWord/Writer/HTML/Element/Container.php index 147329dd85..6e2569f3c6 100644 --- a/src/PhpWord/Writer/HTML/Element/Container.php +++ b/src/PhpWord/Writer/HTML/Element/Container.php @@ -1,4 +1,5 @@ getElements(); @@ -53,7 +54,7 @@ public function write() $elementClass = get_class($element); $writerClass = str_replace('PhpOffice\\PhpWord\\Element', $this->namespace, $elementClass); if (class_exists($writerClass)) { - /** @var \PhpOffice\PhpWord\Writer\HTML\Element\AbstractElement $writer Type hint */ + /** @var AbstractElement $writer Type hint */ $writer = new $writerClass($this->parentWriter, $element, $withoutP); $content .= $writer->write(); } diff --git a/src/PhpWord/Writer/HTML/Element/Endnote.php b/src/PhpWord/Writer/HTML/Element/Endnote.php index 3da8a8fb34..7e7f31d4fc 100644 --- a/src/PhpWord/Writer/HTML/Element/Endnote.php +++ b/src/PhpWord/Writer/HTML/Element/Endnote.php @@ -1,4 +1,5 @@ element instanceof ImageElement) { return ''; } - /** @var \PhpOffice\PhpWord\Writer\HTML $parentWriter Type hint */ - $parentWriter = $this->parentWriter; - $content = ''; - if (!$parentWriter->isPdf()) { - $imageData = $this->element->getImageStringData(true); - if ($imageData !== null) { - $styleWriter = new ImageStyleWriter($this->element->getStyle()); - $style = $styleWriter->write(); - $imageData = 'data:' . $this->element->getImageType() . ';base64,' . $imageData; + $imageData = $this->element->getImageStringData(true); + if ($imageData !== null) { + $styleWriter = new ImageStyleWriter($this->element->getStyle()); + $style = $styleWriter->write(); + $imageData = 'data:' . $this->element->getImageType() . ';base64,' . $imageData; - $content .= $this->writeOpening(); - $content .= ""; - $content .= $this->writeClosing(); - } + $content .= $this->writeOpening(); + $content .= ""; + $content .= $this->writeClosing(); } return $content; diff --git a/src/PhpWord/Writer/HTML/Element/Link.php b/src/PhpWord/Writer/HTML/Element/Link.php index 4e99810c7b..a9f609f6e2 100644 --- a/src/PhpWord/Writer/HTML/Element/Link.php +++ b/src/PhpWord/Writer/HTML/Element/Link.php @@ -1,4 +1,5 @@ writeOpening(); - $content .= "element->getSource()}\">{$this->element->getText()}"; + $prefix = $this->element->isInternal() ? '#' : ''; + $content = $this->writeOpening(); + $content .= "parentWriter->escapeHTML($this->element->getSource()) + . '">' + . $this->parentWriter->escapeHTML($this->element->getText()) + . ''; $content .= $this->writeClosing(); return $content; diff --git a/src/PhpWord/Writer/HTML/Element/ListItem.php b/src/PhpWord/Writer/HTML/Element/ListItem.php index ef8eb34d75..232a894a59 100644 --- a/src/PhpWord/Writer/HTML/Element/ListItem.php +++ b/src/PhpWord/Writer/HTML/Element/ListItem.php @@ -1,4 +1,5 @@ element->getTextObject()->getText(); - $content = '

      ' . $text . '

      ' . PHP_EOL; + $content = '

      ' . $this->parentWriter->escapeHTML($this->element->getTextObject()->getText()) . '

      ' . PHP_EOL; return $content; } diff --git a/tests/PhpWord/Tests/Writer/HTML/PartTest.php b/src/PhpWord/Writer/HTML/Element/ListItemRun.php similarity index 54% rename from tests/PhpWord/Tests/Writer/HTML/PartTest.php rename to src/PhpWord/Writer/HTML/Element/ListItemRun.php index 93e9a98ef8..a708868c3d 100644 --- a/tests/PhpWord/Tests/Writer/HTML/PartTest.php +++ b/src/PhpWord/Writer/HTML/Element/ListItemRun.php @@ -1,4 +1,5 @@ getParentWriter(); + if (!$this->element instanceof \PhpOffice\PhpWord\Element\ListItemRun) { + return ''; + } + + $writer = new Container($this->parentWriter, $this->element); + $content = $writer->write() . PHP_EOL; + + return $content; } } diff --git a/src/PhpWord/Writer/HTML/Element/PageBreak.php b/src/PhpWord/Writer/HTML/Element/PageBreak.php index 774ed9d23c..60335591c5 100644 --- a/src/PhpWord/Writer/HTML/Element/PageBreak.php +++ b/src/PhpWord/Writer/HTML/Element/PageBreak.php @@ -1,4 +1,5 @@ parentWriter; + if ($parentWriter instanceof TCPDF) { + return '
      '; + } if ($parentWriter->isPdf()) { return ''; } - return ""; + return '
       
      ' . PHP_EOL; } } diff --git a/src/PhpWord/Writer/HTML/Element/Ruby.php b/src/PhpWord/Writer/HTML/Element/Ruby.php new file mode 100644 index 0000000000..5648d85e2f --- /dev/null +++ b/src/PhpWord/Writer/HTML/Element/Ruby.php @@ -0,0 +1,126 @@ +processFontStyle(); + + /** @var \PhpOffice\PhpWord\Element\Ruby $element Type hint */ + $element = $this->element; + + $baseText = $this->parentWriter->escapeHTML($element->getBaseTextRun()->getText()); + $rubyText = $this->parentWriter->escapeHTML($element->getRubyTextRun()->getText()); + + $rubyTagPropertyCSS = $this->getPropertyCssForRubyTag($element->getProperties()); + $lang = $element->getProperties()->getLanguageId(); + $content = "getParagraphStyleForTextRun($element->getBaseTextRun(), $rubyTagPropertyCSS)} lang=\"{$lang}\">"; + $content .= $baseText; + $content .= ' ('; + $rtTagPropertyCSS = $this->getPropertyCssForRtTag($element->getProperties()); + $content .= "getParagraphStyleForTextRun($element->getRubyTextRun(), $rtTagPropertyCSS)}>"; + $content .= $rubyText; + $content .= ''; + $content .= ')'; + $content .= ''; + + return $content; + } + + /** + * Get property CSS for the tag. + */ + private function getPropertyCssForRubyTag(RubyProperties $properties): string + { + // alignment CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-align + $alignment = 'space-between'; + switch ($properties->getAlignment()) { + case RubyProperties::ALIGNMENT_CENTER: + $alignment = 'center'; + + break; + case RubyProperties::ALIGNMENT_LEFT: + $alignment = 'start'; + + break; + default: + $alignment = 'space-between'; + + break; + } + + return + 'font-size:' . $properties->getFontSizeForBaseText() . 'pt' . ';' . + 'ruby-align:' . $alignment . ';'; + } + + /** + * Get property CSS for the tag. + */ + private function getPropertyCssForRtTag(RubyProperties $properties): string + { + // alignment CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-align + return 'font-size:' . $properties->getFontFaceSize() . 'pt' . ';'; + } + + /** + * Write paragraph style for a given TextRun. + */ + private function getParagraphStyleForTextRun(TextRun $textRun, string $extraCSS): string + { + $style = ''; + $paragraphStyle = $textRun->getParagraphStyle(); + $pStyleIsObject = ($paragraphStyle instanceof Paragraph); + if ($pStyleIsObject) { + $styleWriter = new ParagraphStyleWriter($paragraphStyle); + $styleWriter->setParentWriter($this->parentWriter); + $style = $styleWriter->write(); + } elseif (is_string($paragraphStyle)) { + $style = $paragraphStyle; + } + if ($style !== null && $style !== '') { + if ($pStyleIsObject) { + // CSS pairs (style="...") + $style = " style=\"{$style}{$extraCSS}\""; + } else { + // class name; need to append extra styles manually + $style = " class=\"{$style}\" style=\"{$extraCSS}\""; + } + } elseif ($extraCSS !== '') { + $style = " style=\"{$extraCSS}\""; + } + + return $style; + } +} diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index 9027603b8f..b66e2f8c92 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -1,4 +1,5 @@ element->getRows(); $rowCount = count($rows); if ($rowCount > 0) { - $content .= '' . PHP_EOL; - foreach ($rows as $row) { - /** @var $row \PhpOffice\PhpWord\Element\Row Type hint */ - $rowStyle = $row->getStyle(); + $content .= 'getTableStyle($this->element->getStyle()) . '>' . PHP_EOL; + + for ($i = 0; $i < $rowCount; ++$i) { + /** @var \PhpOffice\PhpWord\Element\Row $row Type hint */ + $rowStyle = $rows[$i]->getStyle(); // $height = $row->getHeight(); $tblHeader = $rowStyle->isTblHeader(); $content .= '' . PHP_EOL; - foreach ($row->getCells() as $cell) { - $writer = new Container($this->parentWriter, $cell); - $cellTag = $tblHeader ? 'th' : 'td'; - $content .= "<{$cellTag}>" . PHP_EOL; - $content .= $writer->write(); - $content .= "" . PHP_EOL; + $rowCells = $rows[$i]->getCells(); + $rowCellCount = count($rowCells); + for ($j = 0; $j < $rowCellCount; ++$j) { + $cellStyle = $rowCells[$j]->getStyle(); + $cellStyleCss = $this->getTableStyle($cellStyle); + $cellBgColor = $cellStyle->getBgColor(); + $cellFgColor = null; + if ($cellBgColor && $cellBgColor !== 'auto') { + $red = hexdec(substr($cellBgColor, 0, 2)); + $green = hexdec(substr($cellBgColor, 2, 2)); + $blue = hexdec(substr($cellBgColor, 4, 2)); + $cellFgColor = (($red * 0.299 + $green * 0.587 + $blue * 0.114) > 186) ? null : 'ffffff'; + } + $cellColSpan = $cellStyle->getGridSpan(); + $cellRowSpan = 1; + $cellVMerge = $cellStyle->getVMerge(); + // If this is the first cell of the vertical merge, find out how many rows it spans + if ($cellVMerge === 'restart') { + $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 = (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]) && $kRowCells[$j]->getStyle()->getVMerge() === 'continue') { + $writer = new Container($this->parentWriter, $kRowCells[$j]); + $content .= $writer->write(); + } else { + break; + } + } + } + $content .= "" . PHP_EOL; + } } $content .= '' . PHP_EOL; } @@ -60,4 +102,81 @@ public function write() return $content; } + + /** + * Translates Table style in CSS equivalent. + * + * @param null|\PhpOffice\PhpWord\Style\Cell|\PhpOffice\PhpWord\Style\Table|string $tableStyle + */ + private function getTableStyle($tableStyle = null): string + { + if ($tableStyle == null) { + return ''; + } + if (is_string($tableStyle)) { + 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 $rowSpan; + } } diff --git a/src/PhpWord/Writer/HTML/Element/Text.php b/src/PhpWord/Writer/HTML/Element/Text.php index 0c31df3632..7be95a5c76 100644 --- a/src/PhpWord/Writer/HTML/Element/Text.php +++ b/src/PhpWord/Writer/HTML/Element/Text.php @@ -1,4 +1,5 @@ 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; - $content .= $element->getText(); + $content .= $text; $content .= $this->closingTags; $content .= $this->closingText; $content .= $this->writeClosing(); @@ -84,9 +94,8 @@ public function write() * Set opening text. * * @param string $value - * @return void */ - public function setOpeningText($value) + public function setOpeningText($value): void { $this->openingText = $value; } @@ -95,15 +104,14 @@ public function setOpeningText($value) * Set closing text. * * @param string $value - * @return void */ - public function setClosingText($value) + public function setClosingText($value): void { $this->closingText = $value; } /** - * Write opening + * Write opening. * * @return string */ @@ -111,34 +119,95 @@ protected function writeOpening() { $content = ''; if (!$this->withoutP) { - $style = ''; - if (method_exists($this->element, 'getParagraphStyle')) { - $style = $this->getParagraphStyle(); - } + $style = $this->getParagraphStyle(); $content .= ""; } + //open track change tag + $content .= $this->writeTrackChangeOpening(); + return $content; } /** - * Write ending + * Write ending. * * @return string */ protected function writeClosing() { $content = ''; + + //close track change tag + $content .= $this->writeTrackChangeClosing(); + if (!$this->withoutP) { - $content .= $this->closingText; - $content .= "

      " . PHP_EOL; + $content .= $this->parentWriter->escapeHTML($this->closingText); + $content .= '

      ' . PHP_EOL; + } + + return $content; + } + + /** + * writes the track change opening tag. + * + * @return string the HTML, an empty string if no track change information + */ + private function writeTrackChangeOpening() + { + $changed = $this->element->getTrackChange(); + if ($changed == null) { + return ''; + } + + $content = ''; + if (($changed->getChangeType() == TrackChange::INSERTED)) { + $content .= 'getChangeType() == TrackChange::DELETED) { + $content .= ' ['author' => $changed->getAuthor(), 'id' => $this->element->getElementId()]]; + if ($changed->getDate() != null) { + $changedProp['changed']['date'] = $changed->getDate()->format('Y-m-d\TH:i:s\Z'); + } + $content .= json_encode($changedProp); + $content .= '\' '; + $content .= 'title="' . $changed->getAuthor(); + if ($changed->getDate() != null) { + $dateUser = $changed->getDate()->format('Y-m-d H:i:s'); + $content .= ' - ' . $dateUser; + } + $content .= '">'; + + return $content; + } + + /** + * writes the track change closing tag. + * + * @return string the HTML, an empty string if no track change information + */ + private function writeTrackChangeClosing() + { + $changed = $this->element->getTrackChange(); + if ($changed == null) { + return ''; + } + + $content = ''; + if (($changed->getChangeType() == TrackChange::INSERTED)) { + $content .= ''; + } elseif ($changed->getChangeType() == TrackChange::DELETED) { + $content .= ''; } return $content; } /** - * Write paragraph style + * Write paragraph style. * * @return string */ @@ -155,7 +224,10 @@ 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; } if ($style) { $attribute = $pStyleIsObject ? 'style' : 'class'; @@ -167,24 +239,52 @@ private function getParagraphStyle() /** * Get font style. - * - * @return void */ - private function getFontStyle() + 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(); + $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 = ""; - $this->closingTags = ""; + + if ($lang) { + $attributeLang = $lang->getLatin(); + if (!$attributeLang) { + $attributeLang = $lang->getEastAsia(); + } + if (!$attributeLang) { + $attributeLang = $lang->getBidirectional(); + } + if ($attributeLang) { + $attributeLang = " lang='$attributeLang'"; + } + } + + if ($attributeStyle || $attributeLang) { + $this->openingTags = ""; + $this->closingTags = ''; } } } diff --git a/src/PhpWord/Writer/HTML/Element/TextBreak.php b/src/PhpWord/Writer/HTML/Element/TextBreak.php index 30560e9684..576f6a8364 100644 --- a/src/PhpWord/Writer/HTML/Element/TextBreak.php +++ b/src/PhpWord/Writer/HTML/Element/TextBreak.php @@ -1,4 +1,5 @@ element->getDepth(); + $text = $this->element->getText(); + if (is_string($text)) { + $text = $this->parentWriter->escapeHTML($text); + } else { + $writer = new Container($this->parentWriter, $text); + $text = $writer->write(); + } + $content = "<{$tag}>{$text}" . PHP_EOL; return $content; diff --git a/src/PhpWord/Writer/HTML/Part/AbstractPart.php b/src/PhpWord/Writer/HTML/Part/AbstractPart.php index 124cc15a8a..61bf20b29a 100644 --- a/src/PhpWord/Writer/HTML/Part/AbstractPart.php +++ b/src/PhpWord/Writer/HTML/Part/AbstractPart.php @@ -1,4 +1,5 @@ parentWriter = $writer; } /** - * Get parent writer - * - * @return \PhpOffice\PhpWord\Writer\AbstractWriter - * @throws \PhpOffice\PhpWord\Exception\Exception + * @return HTML */ public function getParentWriter() { if ($this->parentWriter !== null) { return $this->parentWriter; - } else { - throw new Exception('No parent WriterInterface assigned.'); } + + throw new Exception('No parent WriterInterface assigned.'); } } diff --git a/src/PhpWord/Writer/HTML/Part/Body.php b/src/PhpWord/Writer/HTML/Part/Body.php index b91ca3ad2d..bad1415c21 100644 --- a/src/PhpWord/Writer/HTML/Part/Body.php +++ b/src/PhpWord/Writer/HTML/Part/Body.php @@ -1,4 +1,5 @@ ' . PHP_EOL; $sections = $phpWord->getSections(); + $secno = 0; + $isTCPDFWriter = $this->getParentWriter() instanceof TCPDF; foreach ($sections as $section) { + ++$secno; + if ($isTCPDFWriter && $secno > 1) { + $content .= "
      " . PHP_EOL; + } else { + $content .= "
      " . PHP_EOL; + } $writer = new Container($this->getParentWriter(), $section); $content .= $writer->write(); + $content .= '
      ' . PHP_EOL; } $content .= $this->writeNotes(); @@ -52,7 +63,7 @@ public function write() } /** - * Write footnote/endnote contents as textruns + * Write footnote/endnote contents as textruns. * * @return string */ @@ -66,9 +77,9 @@ private function writeNotes() $content = ''; if (!empty($notes)) { - $content .= "
      " . PHP_EOL; + $content .= '
      ' . PHP_EOL; foreach ($notes as $noteId => $noteMark) { - list($noteType, $noteTypeId) = explode('-', $noteMark); + [$noteType, $noteTypeId] = explode('-', $noteMark); $method = 'get' . ($noteType == 'endnote' ? 'Endnotes' : 'Footnotes'); $collection = $phpWord->$method()->getItems(); diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php index 503f75b879..79235e1c4a 100644 --- a/src/PhpWord/Writer/HTML/Part/Head.php +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -1,4 +1,5 @@ getParentWriter()->getPhpWord()->getDocInfo(); - $propertiesMapping = array( + $propertiesMapping = [ 'creator' => 'author', 'title' => '', 'description' => '', @@ -48,8 +52,8 @@ public function write() 'keywords' => '', 'category' => '', 'company' => '', - 'manager' => '' - ); + 'manager' => '', + ]; $title = $docProps->getTitle(); $title = ($title != '') ? $title : 'PHPWord'; @@ -60,10 +64,13 @@ public function write() $content .= '' . $title . '' . PHP_EOL; foreach ($propertiesMapping as $key => $value) { $value = ($value == '') ? $key : $value; - $method = "get" . $key; + $method = 'get' . $key; if ($docProps->$method() != '') { - $content .= '' . PHP_EOL; + $content .= '' . PHP_EOL; } } $content .= $this->writeStyles(); @@ -73,40 +80,48 @@ public function write() } /** - * Get styles - * - * @return string + * Get styles. */ - private function writeStyles() + private function writeStyles(): string { $css = '' . 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 07ef16184c..4672347ba2 100644 --- a/src/PhpWord/Writer/HTML/Style/AbstractStyle.php +++ b/src/PhpWord/Writer/HTML/Style/AbstractStyle.php @@ -1,4 +1,5 @@ parentWriter = $writer; } /** - * Get parent writer + * Get parent writer. * - * @return \PhpOffice\PhpWord\Writer\AbstractWriter + * @return HTML */ public function getParentWriter() { @@ -77,13 +80,13 @@ public function getParentWriter() } /** - * Get style + * 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 ''; } @@ -91,14 +94,15 @@ public function getStyle() } /** - * Takes array where of CSS properties / values and converts to CSS string + * Takes array where of CSS properties / values and converts to CSS string. * * @param array $css + * * @return string */ protected function assembleCss($css) { - $pairs = array(); + $pairs = []; $string = ''; foreach ($css as $key => $value) { if ($value != '') { @@ -115,8 +119,9 @@ protected function assembleCss($css) /** * Get value if ... * - * @param bool|null $condition + * @param null|bool $condition * @param string $value + * * @return string */ protected function getValueIf($condition, $value) diff --git a/src/PhpWord/Writer/HTML/Style/Font.php b/src/PhpWord/Writer/HTML/Style/Font.php index 8645a1f402..1001b64d2e 100644 --- a/src/PhpWord/Writer/HTML/Style/Font.php +++ b/src/PhpWord/Writer/HTML/Style/Font.php @@ -1,4 +1,5 @@ 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); $css['font-weight'] = $this->getValueIf($style->isBold(), 'bold'); $css['font-style'] = $this->getValueIf($style->isItalic(), 'italic'); - $css['vertical-align'] = $this->getValueIf($style->isSuperScript(), 'italic'); - $css['vertical-align'] = $this->getValueIf($style->isSuperScript(), 'super'); - $css['vertical-align'] = $this->getValueIf($style->isSubScript(), 'sub'); + $css['vertical-align'] = ''; + $css['vertical-align'] .= $this->getValueIf($style->isSuperScript(), 'super'); + $css['vertical-align'] .= $this->getValueIf($style->isSubScript(), 'sub'); $css['text-decoration'] = ''; $css['text-decoration'] .= $this->getValueIf($underline, 'underline '); $css['text-decoration'] .= $this->getValueIf($lineThrough, 'line-through '); + $css['text-transform'] = $this->getValueIf($style->isAllCaps(), 'uppercase'); + $css['font-variant'] = $this->getValueIf($style->isSmallCaps(), 'small-caps'); + $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/Generic.php b/src/PhpWord/Writer/HTML/Style/Generic.php index df94d4922e..21603a7be1 100644 --- a/src/PhpWord/Writer/HTML/Style/Generic.php +++ b/src/PhpWord/Writer/HTML/Style/Generic.php @@ -1,4 +1,5 @@ getStyle(); - $css = array(); + $css = []; if (is_array($style) && !empty($style)) { $css = $style; diff --git a/src/PhpWord/Writer/HTML/Style/Image.php b/src/PhpWord/Writer/HTML/Style/Image.php index 13be3665cd..30fb373018 100644 --- a/src/PhpWord/Writer/HTML/Style/Image.php +++ b/src/PhpWord/Writer/HTML/Style/Image.php @@ -1,4 +1,5 @@ getWidth(); $height = $style->getHeight(); diff --git a/src/PhpWord/Writer/HTML/Style/Paragraph.php b/src/PhpWord/Writer/HTML/Style/Paragraph.php index 8b326a5cfa..22267e7b18 100644 --- a/src/PhpWord/Writer/HTML/Style/Paragraph.php +++ b/src/PhpWord/Writer/HTML/Style/Paragraph.php @@ -1,4 +1,5 @@ getAlign(); - $css['text-align'] = $this->getValueIf(!is_null($align), $align); + if ('' !== $style->getAlignment()) { + $textAlign = ''; + + switch ($style->getAlignment()) { + case Jc::CENTER: + $textAlign = 'center'; + + break; + case Jc::END: + $textAlign = $style->isBidi() ? 'left' : 'right'; + + break; + case Jc::MEDIUM_KASHIDA: + case Jc::HIGH_KASHIDA: + case Jc::LOW_KASHIDA: + case Jc::RIGHT: + $textAlign = 'right'; + + break; + case Jc::BOTH: + case Jc::DISTRIBUTE: + case Jc::THAI_DISTRIBUTE: + case Jc::JUSTIFY: + $textAlign = 'justify'; + + break; + case Jc::LEFT: + $textAlign = 'left'; + + break; + default: //all others, including Jc::START + $textAlign = $style->isBidi() ? 'right' : 'left'; + + break; + } + + $css['text-align'] = $textAlign; + } // Spacing $spacing = $style->getSpace(); - if (!is_null($spacing)) { + if (null !== $spacing) { $before = $spacing->getBefore(); $after = $spacing->getAfter(); - $css['margin-top'] = $this->getValueIf(!is_null($before), ($before / 20) . 'pt'); - $css['margin-bottom'] = $this->getValueIf(!is_null($after), ($after / 20) . 'pt'); + $css['margin-top'] = $this->getValueIf(null !== $before, ($before / 20) . 'pt'); + $css['margin-bottom'] = $this->getValueIf(null !== $after, ($after / 20) . 'pt'); + } + + // 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..6d3e43e812 --- /dev/null +++ b/src/PhpWord/Writer/HTML/Style/Table.php @@ -0,0 +1,86 @@ +getStyle(); + if (!$style instanceof StyleTable && !$style instanceof StyleCell) { + return ''; + } + + $css = []; + if (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 (method_exists($style, 'isBidiVisual')) { + if ($style->isBidiVisual()) { + $css['direction'] = 'rtl'; + } + } + if (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 8fa364bec9..c9a524e882 100644 --- a/src/PhpWord/Writer/ODText.php +++ b/src/PhpWord/Writer/ODText.php @@ -1,4 +1,5 @@ setPhpWord($phpWord); // Create parts - $this->parts = array( - 'Mimetype' => 'mimetype', - 'Content' => 'content.xml', - 'Meta' => 'meta.xml', - 'Styles' => 'styles.xml', - 'Manifest' => 'META-INF/manifest.xml', - ); + $this->parts = [ + 'Mimetype' => 'mimetype', + 'Content' => 'content.xml', + 'Meta' => 'meta.xml', + 'Styles' => 'styles.xml', + 'Manifest' => 'META-INF/manifest.xml', + ]; foreach (array_keys($this->parts) as $partName) { - $partClass = get_class($this) . '\\Part\\' . $partName; + $partClass = static::class . '\\Part\\' . $partName; if (class_exists($partClass)) { - /** @var $partObject \PhpOffice\PhpWord\Writer\ODText\Part\AbstractPart Type hint */ + /** @var AbstractPart $partObject Type hint */ $partObject = new $partClass(); $partObject->setParentWriter($this); $this->writerParts[strtolower($partName)] = $partObject; @@ -56,16 +64,13 @@ public function __construct(PhpWord $phpWord = null) } // Set package paths - $this->mediaPaths = array('image' => 'Pictures/'); + $this->mediaPaths = ['image' => 'Pictures/']; } /** * Save PhpWord to file. - * - * @param string $filename - * @return void */ - public function save($filename = null) + public function save(string $filename): void { $filename = $this->getTempFile($filename); $zip = $this->getZipArchive($filename); @@ -78,8 +83,28 @@ public function save($filename = null) // 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/AbstractElement.php b/src/PhpWord/Writer/ODText/Element/AbstractElement.php index 9fb24364a3..97d1875cd1 100644 --- a/src/PhpWord/Writer/ODText/Element/AbstractElement.php +++ b/src/PhpWord/Writer/ODText/Element/AbstractElement.php @@ -1,4 +1,5 @@ startElement('text:s'); + $xmlWriter->writeAttributeIf($num > 1, 'text:c', "$num"); + $xmlWriter->endElement(); + $text = preg_replace('/^ +/', '', $text); + } + preg_match_all('/([\\s\\S]*?)(\\t| +| ?$)/', $text, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $this->writeText($match[1]); + if ($match[2] === '') { + break; + } elseif ($match[2] === "\t") { + $xmlWriter->writeElement('text:tab'); + } elseif ($match[2] === ' ') { + $xmlWriter->writeElement('text:s'); + + break; + } else { + $num = strlen($match[2]); + $xmlWriter->startElement('text:s'); + $xmlWriter->writeAttributeIf($num > 1, 'text:c', "$num"); + $xmlWriter->endElement(); + } + } + } } diff --git a/src/PhpWord/Writer/ODText/Element/Container.php b/src/PhpWord/Writer/ODText/Element/Container.php index 8b7807d68f..6b0fee8c64 100644 --- a/src/PhpWord/Writer/ODText/Element/Container.php +++ b/src/PhpWord/Writer/ODText/Element/Container.php @@ -1,4 +1,5 @@ + */ + protected $containerWithoutP = ['TextRun', 'Footnote', 'Endnote']; } diff --git a/src/PhpWord/Writer/ODText/Element/Field.php b/src/PhpWord/Writer/ODText/Element/Field.php new file mode 100644 index 0000000000..2f81eb3f3d --- /dev/null +++ b/src/PhpWord/Writer/ODText/Element/Field.php @@ -0,0 +1,99 @@ +getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Field) { + return; + } + + $type = strtolower($element->getType()); + switch ($type) { + case 'date': + case 'page': + case 'numpages': + case 'filename': + $this->writeDefault($element, $type); + + break; + } + } + + private function writeDefault(\PhpOffice\PhpWord\Element\Field $element, $type): void + { + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('text:span'); + + $fstyle = $element->getFontStyle(); + if (is_string($fstyle)) { + $xmlWriter->writeAttribute('text:style-name', $fstyle); + } + + switch ($type) { + case 'date': + $xmlWriter->startElement('text:date'); + $xmlWriter->writeAttribute('text:fixed', 'false'); + $xmlWriter->endElement(); + + break; + case 'page': + $xmlWriter->startElement('text:page-number'); + $xmlWriter->writeAttribute('text:fixed', 'false'); + $xmlWriter->endElement(); + + break; + case 'numpages': + $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..2c7ce3aaf1 --- /dev/null +++ b/src/PhpWord/Writer/ODText/Element/Formula.php @@ -0,0 +1,75 @@ +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 3cbb38542a..baa3c8cdb6 100644 --- a/src/PhpWord/Writer/ODText/Element/Image.php +++ b/src/PhpWord/Writer/ODText/Element/Image.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\Image) { + if (!$element instanceof ElementImage) { return; } @@ -43,11 +44,16 @@ public function write() $width = Converter::pixelToCm($style->getWidth()); $height = Converter::pixelToCm($style->getHeight()); - $xmlWriter->startElement('text:p'); - $xmlWriter->writeAttribute('text:style-name', 'Standard'); + $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 +69,8 @@ public function write() $xmlWriter->endElement(); // draw:frame - $xmlWriter->endElement(); // text:p + if (!$this->withoutP) { + $xmlWriter->endElement(); // text:p + } } } diff --git a/src/PhpWord/Writer/ODText/Element/Link.php b/src/PhpWord/Writer/ODText/Element/Link.php index adb64f8599..9ef35692c8 100644 --- a/src/PhpWord/Writer/ODText/Element/Link.php +++ b/src/PhpWord/Writer/ODText/Element/Link.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -41,8 +42,8 @@ public function write() $xmlWriter->startElement('text:a'); $xmlWriter->writeAttribute('xlink:type', 'simple'); - $xmlWriter->writeAttribute('xlink:href', $element->getSource()); - $xmlWriter->writeRaw($element->getText()); + $xmlWriter->writeAttribute('xlink:href', ($element->isInternal() ? '#' : '') . $element->getSource()); + $this->writeText($element->getText()); $xmlWriter->endElement(); // text:a if (!$this->withoutP) { diff --git a/src/PhpWord/Writer/ODText/Element/ListItemRun.php b/src/PhpWord/Writer/ODText/Element/ListItemRun.php new file mode 100644 index 0000000000..1319e48577 --- /dev/null +++ b/src/PhpWord/Writer/ODText/Element/ListItemRun.php @@ -0,0 +1,57 @@ +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/PageBreak.php b/src/PhpWord/Writer/ODText/Element/PageBreak.php new file mode 100644 index 0000000000..ca9e53f4a6 --- /dev/null +++ b/src/PhpWord/Writer/ODText/Element/PageBreak.php @@ -0,0 +1,37 @@ +getXmlWriter(); + + $xmlWriter->startElement('text:p'); + $xmlWriter->writeAttribute('text:style-name', 'PB'); + $xmlWriter->endElement(); + } +} diff --git a/src/PhpWord/Writer/ODText/Element/Ruby.php b/src/PhpWord/Writer/ODText/Element/Ruby.php new file mode 100644 index 0000000000..41a86776d4 --- /dev/null +++ b/src/PhpWord/Writer/ODText/Element/Ruby.php @@ -0,0 +1,64 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Ruby) { + return; + } + $paragraphStyle = $element->getBaseTextRun()->getParagraphStyle(); + + if (!$this->withoutP) { + $xmlWriter->startElement('text:p'); // text:p + } + if (empty($paragraphStyle)) { + if (!$this->withoutP) { + $xmlWriter->writeAttribute('text:style-name', 'Normal'); + } + } elseif (is_string($paragraphStyle)) { + if (!$this->withoutP) { + $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); + } + } + + $this->replaceTabs($element->getBaseTextRun()->getText(), $xmlWriter); + $this->writeText(' ('); + $this->replaceTabs($element->getRubyTextRun()->getText(), $xmlWriter); + $this->writeText(')'); + + if (!$this->withoutP) { + $xmlWriter->endElement(); // text:p + } + } +} diff --git a/src/PhpWord/Writer/ODText/Element/Table.php b/src/PhpWord/Writer/ODText/Element/Table.php index f26960b828..097f6742bb 100644 --- a/src/PhpWord/Writer/ODText/Element/Table.php +++ b/src/PhpWord/Writer/ODText/Element/Table.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\Table) { + if (!$element instanceof TableElement) { return; } $rows = $element->getRows(); $rowCount = count($rows); - $colCount = $element->countColumns(); 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()); - $xmlWriter->startElement('table:table-column'); - $xmlWriter->writeAttribute('table:number-columns-repeated', $colCount); - $xmlWriter->endElement(); // table:table-column + // Write columns + $this->writeColumns($xmlWriter, $element); + // Write rows foreach ($rows as $row) { - $xmlWriter->startElement('table:table-row'); - /** @var $row \PhpOffice\PhpWord\Element\Row Type hint */ - foreach ($row->getCells() as $cell) { - $xmlWriter->startElement('table:table-cell'); - $xmlWriter->writeAttribute('office:value-type', 'string'); - - $containerWriter = new Container($xmlWriter, $cell); - $containerWriter->write(); - - $xmlWriter->endElement(); // table:table-cell - } - $xmlWriter->endElement(); // table:table-row + $this->writeRow($xmlWriter, $row); } $xmlWriter->endElement(); // table:table } } + + /** + * Write column. + */ + private function writeColumns(XMLWriter $xmlWriter, TableElement $element): void + { + $colCount = $element->countColumns(); + + for ($i = 0; $i < $colCount; ++$i) { + $xmlWriter->startElement('table:table-column'); + $xmlWriter->writeAttribute('table:style-name', $element->getElementId() . '.' . $i); + $xmlWriter->endElement(); + } + } + + /** + * Write row. + */ + private function writeRow(XMLWriter $xmlWriter, RowElement $row): void + { + $xmlWriter->startElement('table:table-row'); + /** @var RowElement $row Type hint */ + foreach ($row->getCells() as $cell) { + $xmlWriter->startElement('table:table-cell'); + $xmlWriter->writeAttribute('office:value-type', 'string'); + + $containerWriter = new Container($xmlWriter, $cell); + $containerWriter->write(); + + $xmlWriter->endElement(); // table:table-cell + } + $xmlWriter->endElement(); // table:table-row + } } diff --git a/src/PhpWord/Writer/ODText/Element/Text.php b/src/PhpWord/Writer/ODText/Element/Text.php index 4fc53bfee5..3996972387 100644 --- a/src/PhpWord/Writer/ODText/Element/Text.php +++ b/src/PhpWord/Writer/ODText/Element/Text.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -41,39 +43,60 @@ public function write() // @todo Commented for TextRun. Should really checkout this value // $fStyleIsObject = ($fontStyle instanceof Font) ? true : false; - $fStyleIsObject = false; + //$fStyleIsObject = false; + + //if ($fStyleIsObject) { + // Don't never be the case, because I browse all sections for cleaning all styles not declared + // throw new Exception('PhpWord : $fStyleIsObject wouldn\'t be an object'); + //} - if ($fStyleIsObject) { - // Don't never be the case, because I browse all sections for cleaning all styles not declared - throw new Exception('PhpWord : $fStyleIsObject wouldn\'t be an object'); + if (!$this->withoutP) { + $xmlWriter->startElement('text:p'); // text:p + } + if ($element->getTrackChange() != null && $element->getTrackChange()->getChangeType() == TrackChange::DELETED) { + $xmlWriter->startElement('text:change'); + $xmlWriter->writeAttribute('text:change-id', $element->getTrackChange()->getElementId()); + $xmlWriter->endElement(); } else { - if (!$this->withoutP) { - $xmlWriter->startElement('text:p'); // text:p - } - if (empty($fontStyle)) { - if (empty($paragraphStyle)) { - $xmlWriter->writeAttribute('text:style-name', 'P1'); - } elseif (is_string($paragraphStyle)) { - $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); + if (empty($paragraphStyle)) { + if (!$this->withoutP) { + $xmlWriter->writeAttribute('text:style-name', 'Normal'); } - $xmlWriter->writeRaw($element->getText()); - } else { - if (empty($paragraphStyle)) { - $xmlWriter->writeAttribute('text:style-name', 'Standard'); - } elseif (is_string($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); } - $xmlWriter->writeRaw($element->getText()); - $xmlWriter->endElement(); } - if (!$this->withoutP) { - $xmlWriter->endElement(); // text:p + + $this->writeChangeInsertion(true, $element->getTrackChange()); + $this->replaceTabs($element->getText(), $xmlWriter); + $this->writeChangeInsertion(false, $element->getTrackChange()); + + if (!empty($fontStyle)) { + $xmlWriter->endElement(); } } + if (!$this->withoutP) { + $xmlWriter->endElement(); // text:p + } + } + + private function writeChangeInsertion($start = true, ?TrackChange $trackChange = null): void + { + if ($trackChange == null || $trackChange->getChangeType() != TrackChange::INSERTED) { + return; + } + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('text:change-' . ($start ? 'start' : 'end')); + $xmlWriter->writeAttribute('text:change-id', $trackChange->getElementId()); + $xmlWriter->endElement(); } } diff --git a/src/PhpWord/Writer/ODText/Element/TextBreak.php b/src/PhpWord/Writer/ODText/Element/TextBreak.php index ef3186f4f2..1a69700792 100644 --- a/src/PhpWord/Writer/ODText/Element/TextBreak.php +++ b/src/PhpWord/Writer/ODText/Element/TextBreak.php @@ -1,4 +1,5 @@ getXmlWriter(); diff --git a/src/PhpWord/Writer/ODText/Element/TextRun.php b/src/PhpWord/Writer/ODText/Element/TextRun.php index 52240e8fc4..9d009c3994 100644 --- a/src/PhpWord/Writer/ODText/Element/TextRun.php +++ b/src/PhpWord/Writer/ODText/Element/TextRun.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); $xmlWriter->startElement('text:p'); + /** @scrutinizer ignore-call */ + $pStyle = $element->getParagraphStyle(); + if (!is_string($pStyle)) { + $pStyle = 'Normal'; + } + $xmlWriter->writeAttribute('text:style-name', $pStyle); $containerWriter = new Container($xmlWriter, $element); $containerWriter->write(); diff --git a/src/PhpWord/Writer/ODText/Element/Title.php b/src/PhpWord/Writer/ODText/Element/Title.php index d45f5e12bc..6108710517 100644 --- a/src/PhpWord/Writer/ODText/Element/Title.php +++ b/src/PhpWord/Writer/ODText/Element/Title.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -36,8 +37,44 @@ public function write() } $xmlWriter->startElement('text:h'); - $xmlWriter->writeAttribute('text:outline-level', $element->getDepth()); - $xmlWriter->writeRaw($element->getText()); + $hdname = 'HD'; + $sect = $element->getParent(); + if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { + if (self::compareToFirstElement($element, $sect->getElements())) { + $hdname = 'HE'; + } + } + $depth = $element->getDepth(); + $xmlWriter->writeAttribute('text:style-name', "$hdname$depth"); + $xmlWriter->writeAttribute('text:outline-level', $depth); + $xmlWriter->startElement('text:span'); + if ($depth > 0) { + $xmlWriter->writeAttribute('text:style-name', 'Heading_' . $depth); + } else { + $xmlWriter->writeAttribute('text:style-name', 'Title'); + } + $text = $element->getText(); + if (is_string($text)) { + $this->writeText($text); + } + if ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { + $containerWriter = new Container($xmlWriter, $text); + $containerWriter->write(); + } + $xmlWriter->endElement(); // text:span $xmlWriter->endElement(); // text:h } + + /** + * Test if element is same as first element in array. + * + * @param \PhpOffice\PhpWord\Element\AbstractElement $elem + * @param \PhpOffice\PhpWord\Element\AbstractElement[] $elemarray + * + * @return bool + */ + private static function compareToFirstElement($elem, $elemarray) + { + return $elem === $elemarray[0]; + } } diff --git a/src/PhpWord/Writer/ODText/Part/AbstractPart.php b/src/PhpWord/Writer/ODText/Part/AbstractPart.php index edc8d07f4f..458831d3ad 100644 --- a/src/PhpWord/Writer/ODText/Part/AbstractPart.php +++ b/src/PhpWord/Writer/ODText/Part/AbstractPart.php @@ -1,4 +1,5 @@ writeAttribute('office:version', '1.2'); $xmlWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'); @@ -72,21 +76,18 @@ protected function writeCommonRootAttributes(XMLWriter $xmlWriter) /** * Write font faces declaration. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @return void */ - protected function writeFontFaces(XMLWriter $xmlWriter) + protected function writeFontFaces(XMLWriter $xmlWriter): void { $xmlWriter->startElement('office:font-face-decls'); - $fontTable = array(); + $fontTable = []; $styles = Style::getStyles(); $numFonts = 0; if (count($styles) > 0) { foreach ($styles as $style) { // Font if ($style instanceof Font) { - $numFonts++; + ++$numFonts; $name = $style->getName(); if (!in_array($name, $fontTable)) { $fontTable[] = $name; @@ -108,4 +109,29 @@ protected function writeFontFaces(XMLWriter $xmlWriter) } $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 4bde66ee97..b4958e84e6 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -1,4 +1,5 @@ array(), 'Image' => array(), 'Table' => array()); + private $autoStyles = ['Section' => [], 'Image' => [], 'Table' => []]; + + private $imageParagraphStyles = []; /** - * Write part + * Write part. * * @return string */ @@ -74,8 +81,43 @@ public function write() $xmlWriter->startElement('office:body'); $xmlWriter->startElement('office:text'); + // Tracked changes declarations + $trackedChanges = []; + $sections = $phpWord->getSections(); + foreach ($sections as $section) { + $this->collectTrackedChanges($section, $trackedChanges); + } + $xmlWriter->startElement('text:tracked-changes'); + foreach ($trackedChanges as $trackedElement) { + $trackedChange = $trackedElement->getTrackChange(); + $xmlWriter->startElement('text:changed-region'); + $trackedChange->setElementId(); + $xmlWriter->writeAttribute('text:id', $trackedChange->getElementId()); + + if (($trackedChange->getChangeType() == TrackChange::INSERTED)) { + $xmlWriter->startElement('text:insertion'); + } elseif ($trackedChange->getChangeType() == TrackChange::DELETED) { + $xmlWriter->startElement('text:deletion'); + } + + $xmlWriter->startElement('office:change-info'); + $xmlWriter->writeElement('dc:creator', $trackedChange->getAuthor()); + if ($trackedChange->getDate() != null) { + $xmlWriter->writeElement('dc:date', $trackedChange->getDate()->format('Y-m-d\TH:i:s\Z')); + } + $xmlWriter->endElement(); // office:change-info + if ($trackedChange->getChangeType() == TrackChange::DELETED && method_exists($trackedElement, 'getText')) { + // @phpstan-ignore-next-line + $xmlWriter->writeElement('text:p', $trackedElement->getText()); + } + + $xmlWriter->endElement(); // text:insertion|text:deletion + $xmlWriter->endElement(); // text:changed-region + } + $xmlWriter->endElement(); // text:tracked-changes + // Sequence declarations - $sequences = array('Illustration', 'Table', 'Text', 'Drawing'); + $sequences = ['Illustration', 'Table', 'Text', 'Drawing']; $xmlWriter->startElement('text:sequence-decls'); foreach ($sequences as $sequence) { $xmlWriter->startElement('text:sequence-decl'); @@ -92,8 +134,14 @@ public function write() $xmlWriter->startElement('text:section'); $xmlWriter->writeAttribute('text:name', $name); $xmlWriter->writeAttribute('text:style-name', $name); + $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 } @@ -109,11 +157,8 @@ public function write() * Write automatic styles other than fonts and paragraphs. * * @since 0.11.0 - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @return void */ - private function writeAutoStyles(XMLWriter $xmlWriter) + private function writeAutoStyles(XMLWriter $xmlWriter): void { $xmlWriter->startElement('office:automatic-styles'); @@ -121,7 +166,6 @@ private function writeAutoStyles(XMLWriter $xmlWriter) foreach ($this->autoStyles as $element => $styles) { $writerClass = 'PhpOffice\\PhpWord\\Writer\\ODText\\Style\\' . $element; foreach ($styles as $style) { - /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */ $styleWriter = new $writerClass($xmlWriter, $style); $styleWriter->write(); @@ -133,45 +177,70 @@ private function writeAutoStyles(XMLWriter $xmlWriter) /** * Write automatic styles. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @return void */ - private function writeTextStyles(XMLWriter $xmlWriter) + private function writeTextStyles(XMLWriter $xmlWriter): void { $styles = Style::getStyles(); $paragraphStyleCount = 0; - if (count($styles) > 0) { - foreach ($styles as $style) { - if ($style->isAuto() === true) { - $styleClass = str_replace('\\Style\\', '\\Writer\\ODText\\Style\\', get_class($style)); - if (class_exists($styleClass)) { - /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */ - $styleWriter = new $styleClass($xmlWriter, $style); - $styleWriter->write(); - } - if ($style instanceof Paragraph) { - $paragraphStyleCount++; - } - } - } - if ($paragraphStyleCount == 0) { + + $style = new Paragraph(); + $style->setStyleName('PB'); + $style->setAuto(); + $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); + $styleWriter->write(); + + $sects = $this->getParentWriter()->getPhpWord()->getSections(); + $countsects = count($sects); + for ($i = 0; $i < $countsects; ++$i) { + $iplus1 = $i + 1; + $style = new Paragraph(); + $style->setStyleName("SB$iplus1"); + $style->setAuto(); + $pnstart = $sects[$i]->getStyle()->getPageNumberingStart(); + $style->setNumLevel($pnstart); + $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); + $styleWriter->write(); + } + + foreach ($styles as $style) { + $sty = (string) $style->getStyleName(); + if (substr($sty, 0, 8) === 'Heading_') { $style = new Paragraph(); - $style->setStyleName('P1'); + $style->setStyleName('HD' . substr($sty, 8)); $style->setAuto(); $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); $styleWriter->write(); + $style = new Paragraph(); + $style->setStyleName('HE' . substr($sty, 8)); + $style->setAuto(); + $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); + $styleWriter->write(); + } + } + + foreach ($styles as $style) { + if ($style->isAuto() === true) { + $styleClass = str_replace('\\Style\\', '\\Writer\\ODText\\Style\\', get_class($style)); + if (class_exists($styleClass)) { + /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */ + $styleWriter = new $styleClass($xmlWriter, $style); + $styleWriter->write(); + } + if ($style instanceof Paragraph) { + ++$paragraphStyleCount; + } } } + foreach ($this->imageParagraphStyles as $style) { + $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); + $styleWriter->write(); + } } /** * Get automatic styles. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord - * @return void */ - private function getAutoStyles(PhpWord $phpWord) + private function getAutoStyles(PhpWord $phpWord): void { $sections = $phpWord->getSections(); $paragraphStyleCount = 0; @@ -185,68 +254,168 @@ private function getAutoStyles(PhpWord $phpWord) } /** - * Get all styles of each elements in container recursively + * Get all styles of each elements in container recursively. * * Table style can be null or string of the style name * - * @param \PhpOffice\PhpWord\Element\AbstractContainer $container - * @param int &$paragraphStyleCount - * @param int &$fontStyleCount - * @return void + * @param AbstractContainer $container + * @param int $paragraphStyleCount + * @param int $fontStyleCount + * * @todo Simplify the logic */ - private function getContainerStyle($container, &$paragraphStyleCount, &$fontStyleCount) + private function getContainerStyle($container, &$paragraphStyleCount, &$fontStyleCount): void { $elements = $container->getElements(); foreach ($elements as $element) { if ($element instanceof TextRun) { + $this->getElementStyleTextRun($element, $paragraphStyleCount); $this->getContainerStyle($element, $paragraphStyleCount, $fontStyleCount); } elseif ($element instanceof Text) { $this->getElementStyle($element, $paragraphStyleCount, $fontStyleCount); + } elseif ($element instanceof Field) { + $this->getElementStyleField($element, $fontStyleCount); } elseif ($element instanceof Image) { $style = $element->getStyle(); $style->setStyleName('fr' . $element->getMediaIndex()); $this->autoStyles['Image'][] = $style; + $sty = new Paragraph(); + $sty->setStyleName('IM' . $element->getMediaIndex()); + $sty->setAuto(); + $sty->setAlignment($style->getAlignment()); + $this->imageParagraphStyles[] = $sty; } elseif ($element instanceof Table) { $style = $element->getStyle(); + if (is_string($style)) { + $style = Style::getStyle($style); + } if ($style === null) { $style = new TableStyle(); - } elseif (is_string($style)) { - $style = Style::getStyle($style); } $style->setStyleName($element->getElementId()); + $style->setColumnWidths($element->findFirstDefinedCellWidths()); $this->autoStyles['Table'][] = $style; } } } /** - * Get style of individual element + * Get style of individual element. * - * @param \PhpOffice\PhpWord\Element\Text &$element - * @param int &$paragraphStyleCount - * @param int &$fontStyleCount - * @return void + * @param Text $element + * @param int $paragraphStyleCount + * @param int $fontStyleCount */ - private function getElementStyle(&$element, &$paragraphStyleCount, &$fontStyleCount) + private function getElementStyle($element, &$paragraphStyleCount, &$fontStyleCount): void { $fontStyle = $element->getFontStyle(); $paragraphStyle = $element->getParagraphStyle(); $phpWord = $this->getParentWriter()->getPhpWord(); - // Font if ($fontStyle instanceof Font) { - $fontStyleCount++; - $style = $phpWord->addFontStyle("T{$fontStyleCount}", $fontStyle); + // Font + $name = $fontStyle->getStyleName(); + if (!$name) { + ++$fontStyleCount; + $style = $phpWord->addFontStyle("T{$fontStyleCount}", $fontStyle, null); + $style->setAuto(); + $style->setParagraph(null); + $element->setFontStyle("T{$fontStyleCount}"); + } else { + $element->setFontStyle($name); + } + } + if ($paragraphStyle instanceof Paragraph) { + // Paragraph + $name = $paragraphStyle->getStyleName(); + if (!$name) { + ++$paragraphStyleCount; + $style = $phpWord->addParagraphStyle("P{$paragraphStyleCount}", $paragraphStyle); + $style->setAuto(); + $element->setParagraphStyle("P{$paragraphStyleCount}"); + } else { + $element->setParagraphStyle($name); + } + } elseif ($paragraphStyle) { + ++$paragraphStyleCount; + $parstylename = "P$paragraphStyleCount" . "_$paragraphStyle"; + $style = $phpWord->addParagraphStyle($parstylename, $paragraphStyle); $style->setAuto(); - $element->setFontStyle("T{$fontStyleCount}"); + $element->setParagraphStyle($parstylename); + } + } + + /** + * Get font style of individual field element. + * + * @param Field $element + * @param int $fontStyleCount + */ + private function getElementStyleField($element, &$fontStyleCount): void + { + $fontStyle = $element->getFontStyle(); + $phpWord = $this->getParentWriter()->getPhpWord(); + + if ($fontStyle instanceof Font) { + $name = $fontStyle->getStyleName(); + if (!$name) { + ++$fontStyleCount; + $style = $phpWord->addFontStyle("T{$fontStyleCount}", $fontStyle, null); + $style->setAuto(); + $style->setParagraph(null); + $element->setFontStyle("T{$fontStyleCount}"); + } else { + $element->setFontStyle($name); + } + } + } - // Paragraph - } elseif ($paragraphStyle instanceof Paragraph) { - $paragraphStyleCount++; - $style = $phpWord->addParagraphStyle("P{$paragraphStyleCount}", array()); + /** + * Get style of individual element. + * + * @param TextRun $element + * @param int $paragraphStyleCount + */ + private function getElementStyleTextRun($element, &$paragraphStyleCount): void + { + $paragraphStyle = $element->getParagraphStyle(); + $phpWord = $this->getParentWriter()->getPhpWord(); + + if ($paragraphStyle instanceof Paragraph) { + // Paragraph + $name = $paragraphStyle->getStyleName(); + if (!$name) { + ++$paragraphStyleCount; + $style = $phpWord->addParagraphStyle("P{$paragraphStyleCount}", $paragraphStyle); + $style->setAuto(); + $element->setParagraphStyle("P{$paragraphStyleCount}"); + } else { + $element->setParagraphStyle($name); + } + } elseif ($paragraphStyle) { + ++$paragraphStyleCount; + $parstylename = "P$paragraphStyleCount" . "_$paragraphStyle"; + $style = $phpWord->addParagraphStyle($parstylename, $paragraphStyle); $style->setAuto(); - $element->setParagraphStyle("P{$paragraphStyleCount}"); + $element->setParagraphStyle($parstylename); + } + } + + /** + * Finds all tracked changes. + * + * @param \PhpOffice\PhpWord\Element\AbstractElement[] $trackedChanges + */ + private function collectTrackedChanges(AbstractContainer $container, &$trackedChanges = []): void + { + $elements = $container->getElements(); + foreach ($elements as $element) { + if ($element->getTrackChange() != null) { + $trackedChanges[] = $element; + } + if (is_callable([$element, 'getElements'])) { + $this->collectTrackedChanges($element, $trackedChanges); + } } } } diff --git a/src/PhpWord/Writer/ODText/Part/Manifest.php b/src/PhpWord/Writer/ODText/Part/Manifest.php index 7c695e9bd4..200da15836 100644 --- a/src/PhpWord/Writer/ODText/Part/Manifest.php +++ b/src/PhpWord/Writer/ODText/Part/Manifest.php @@ -1,4 +1,5 @@ getXmlWriter(); $xmlWriter->startDocument('1.0', 'UTF-8'); @@ -46,7 +48,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 +66,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/Part/Meta.php b/src/PhpWord/Writer/ODText/Part/Meta.php index 15c81a4e9c..21410ba9fd 100644 --- a/src/PhpWord/Writer/ODText/Part/Meta.php +++ b/src/PhpWord/Writer/ODText/Part/Meta.php @@ -1,4 +1,5 @@ writeElement('meta:keyword', $docProps->getKeywords()); // Category, company, and manager are put in meta namespace - $properties = array('Category', 'Company', 'Manager'); + $properties = ['Category', 'Company', 'Manager']; foreach ($properties as $property) { $method = "get{$property}"; if ($docProps->$method() !== null) { @@ -84,23 +85,21 @@ public function write() } /** - * Write individual property + * Write individual property. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param string $property * @param string $value - * @return void * * @todo Handle other `$type`: double|date|dateTime|duration|boolean (4th arguments) */ - private function writeCustomProperty(XMLWriter $xmlWriter, $property, $value) + private function writeCustomProperty(XMLWriter $xmlWriter, $property, $value): void { $xmlWriter->startElement('meta:user-defined'); $xmlWriter->writeAttribute('meta:name', $property); // if ($type !== null) { // $xmlWriter->writeAttribute('meta:value-type', $type); // } - $xmlWriter->writeRaw($value); + $this->writeText($value); $xmlWriter->endElement(); // meta:user-defined } } diff --git a/src/PhpWord/Writer/ODText/Part/Mimetype.php b/src/PhpWord/Writer/ODText/Part/Mimetype.php index 1da4edb0b5..1b94c155b1 100644 --- a/src/PhpWord/Writer/ODText/Part/Mimetype.php +++ b/src/PhpWord/Writer/ODText/Part/Mimetype.php @@ -1,4 +1,5 @@ startElement('office:automatic-styles'); $this->writePageLayout($xmlWriter); + $xmlWriter->endElement(); // office:automatic-styles + + // Master style $this->writeMaster($xmlWriter); - $xmlWriter->endElement(); $xmlWriter->endElement(); // office:document-styles @@ -62,11 +66,8 @@ public function write() /** * Write default styles. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @return void */ - private function writeDefault(XMLWriter $xmlWriter) + private function writeDefault(XMLWriter $xmlWriter): void { $xmlWriter->startElement('style:default-style'); $xmlWriter->writeAttribute('style:family', 'paragraph'); @@ -81,22 +82,31 @@ private function writeDefault(XMLWriter $xmlWriter) $xmlWriter->writeAttribute('style:writing-mode', 'page'); $xmlWriter->endElement(); // style:paragraph-properties + $language = $this->getParentWriter()->getPhpWord()->getSettings()->getThemeFontLang(); + $latinLang = $language != null && is_string($language->getLatin()) ? explode('-', $language->getLatin()) : ['fr', 'FR']; + $asianLang = $language != null && is_string($language->getEastAsia()) ? explode('-', $language->getEastAsia()) : ['zh', 'CN']; + $complexLang = $language != null && is_string($language->getBidirectional()) ? explode('-', $language->getBidirectional()) : ['hi', 'IN']; + if ($this->getParentWriter()->getPhpWord()->getSettings()->hasHideGrammaticalErrors()) { + $latinLang = $asianLang = $complexLang = ['zxx', 'none']; + } + // Font $xmlWriter->startElement('style:text-properties'); - $xmlWriter->writeAttribute('style:use-window-font-color', 'true'); + $xmlWriter->writeAttribute('style:use-window-font-color', 'false'); $xmlWriter->writeAttribute('style:font-name', Settings::getDefaultFontName()); $xmlWriter->writeAttribute('fo:font-size', Settings::getDefaultFontSize() . 'pt'); - $xmlWriter->writeAttribute('fo:language', 'fr'); - $xmlWriter->writeAttribute('fo:country', 'FR'); + $xmlWriter->writeAttribute('fo:language', $latinLang[0]); + $xmlWriter->writeAttribute('fo:country', $latinLang[1]); + $xmlWriter->writeAttribute('fo:color', '#' . Settings::getDefaultFontColor()); $xmlWriter->writeAttribute('style:letter-kerning', 'true'); $xmlWriter->writeAttribute('style:font-name-asian', Settings::getDefaultFontName() . '2'); $xmlWriter->writeAttribute('style:font-size-asian', Settings::getDefaultFontSize() . 'pt'); - $xmlWriter->writeAttribute('style:language-asian', 'zh'); - $xmlWriter->writeAttribute('style:country-asian', 'CN'); + $xmlWriter->writeAttribute('style:language-asian', $asianLang[0]); + $xmlWriter->writeAttribute('style:country-asian', $asianLang[1]); $xmlWriter->writeAttribute('style:font-name-complex', Settings::getDefaultFontName() . '2'); $xmlWriter->writeAttribute('style:font-size-complex', Settings::getDefaultFontSize() . 'pt'); - $xmlWriter->writeAttribute('style:language-complex', 'hi'); - $xmlWriter->writeAttribute('style:country-complex', 'IN'); + $xmlWriter->writeAttribute('style:language-complex', $complexLang[0]); + $xmlWriter->writeAttribute('style:country-complex', $complexLang[1]); $xmlWriter->writeAttribute('fo:hyphenate', 'false'); $xmlWriter->writeAttribute('fo:hyphenation-remain-char-count', '2'); $xmlWriter->writeAttribute('fo:hyphenation-push-char-count', '2'); @@ -107,11 +117,8 @@ private function writeDefault(XMLWriter $xmlWriter) /** * Write named styles. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @return void */ - private function writeNamed(XMLWriter $xmlWriter) + private function writeNamed(XMLWriter $xmlWriter): void { $styles = Style::getStyles(); if (count($styles) > 0) { @@ -119,7 +126,7 @@ private function writeNamed(XMLWriter $xmlWriter) if ($style->isAuto() === false) { $styleClass = str_replace('\\Style\\', '\\Writer\\ODText\\Style\\', get_class($style)); if (class_exists($styleClass)) { - /** @var $styleWriter \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle Type hint */ + /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */ $styleWriter = new $styleClass($xmlWriter, $style); $styleWriter->write(); } @@ -127,26 +134,73 @@ private function writeNamed(XMLWriter $xmlWriter) } } } + + /** + * Convert int in twips to inches/cm then to string and append unit. + * + * @param float|int $twips + * @param float $factor + * return string + */ + private static function cvttwiptostr($twips, $factor = 1.0) + { + $ins = (string) ($twips * $factor / Converter::INCH_TO_TWIP) . 'in'; + $cms = (string) ($twips * $factor * Converter::INCH_TO_CM / Converter::INCH_TO_TWIP) . 'cm'; + + return (strlen($ins) < strlen($cms)) ? $ins : $cms; + } + + /** + * call writePageLayoutIndiv to write page layout styles for each page. + */ + private function writePageLayout(XMLWriter $xmlWriter): void + { + $sections = $this->getParentWriter()->getPhpWord()->getSections(); + $countsects = count($sections); + for ($i = 0; $i < $countsects; ++$i) { + $this->writePageLayoutIndiv($xmlWriter, $sections[$i], $i + 1); + } + } + /** * Write page layout styles. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @return void + * @param \PhpOffice\PhpWord\Element\Section $section + * @param int $sectionNbr */ - private function writePageLayout(XMLWriter $xmlWriter) + private function writePageLayoutIndiv(XMLWriter $xmlWriter, $section, $sectionNbr): void { + $sty = $section->getStyle(); + if (count($section->getHeaders()) > 0) { + $topfactor = 0.5; + } else { + $topfactor = 1.0; + } + if (count($section->getFooters()) > 0) { + $botfactor = 0.5; + } else { + $botfactor = 1.0; + } + $orient = $sty->getOrientation(); + $pwidth = self::cvttwiptostr($sty->getPageSizeW()); + $pheight = self::cvttwiptostr($sty->getPageSizeH()); + $mtop = self::cvttwiptostr($sty->getMarginTop(), $topfactor); + $mbottom = self::cvttwiptostr($sty->getMarginBottom(), $botfactor); + $mleft = self::cvttwiptostr($sty->getMarginRight()); + $mright = self::cvttwiptostr($sty->getMarginLeft()); + $xmlWriter->startElement('style:page-layout'); - $xmlWriter->writeAttribute('style:name', 'Mpm1'); + $xmlWriter->writeAttribute('style:name', "Mpm$sectionNbr"); $xmlWriter->startElement('style:page-layout-properties'); - $xmlWriter->writeAttribute('fo:page-width', "21.001cm"); - $xmlWriter->writeAttribute('fo:page-height', '29.7cm'); + $xmlWriter->writeAttribute('fo:page-width', $pwidth); + $xmlWriter->writeAttribute('fo:page-height', $pheight); $xmlWriter->writeAttribute('style:num-format', '1'); - $xmlWriter->writeAttribute('style:print-orientation', 'portrait'); - $xmlWriter->writeAttribute('fo:margin-top', '2.501cm'); - $xmlWriter->writeAttribute('fo:margin-bottom', '2cm'); - $xmlWriter->writeAttribute('fo:margin-left', '2.501cm'); - $xmlWriter->writeAttribute('fo:margin-right', '2.501cm'); + $xmlWriter->writeAttribute('style:print-orientation', $orient); + $xmlWriter->writeAttribute('fo:margin-top', $mtop); + $xmlWriter->writeAttribute('fo:margin-bottom', $mbottom); + $xmlWriter->writeAttribute('fo:margin-left', $mleft); + $xmlWriter->writeAttribute('fo:margin-right', $mright); $xmlWriter->writeAttribute('style:writing-mode', 'lr-tb'); $xmlWriter->writeAttribute('style:layout-grid-color', '#c0c0c0'); $xmlWriter->writeAttribute('style:layout-grid-lines', '25199'); @@ -170,11 +224,24 @@ private function writePageLayout(XMLWriter $xmlWriter) $xmlWriter->endElement(); // style:page-layout-properties - $xmlWriter->startElement('style:header-style'); + if ($topfactor < 1.0) { + $xmlWriter->startElement('style:header-footer-properties'); + $xmlWriter->writeAttribute('fo:min-height', $mtop); + $xmlWriter->writeAttribute('fo:margin-bottom', $mtop); + $xmlWriter->writeAttribute('style:dynamic-spacing', 'true'); + $xmlWriter->endElement(); // style:header-footer-properties + } $xmlWriter->endElement(); // style:header-style $xmlWriter->startElement('style:footer-style'); + if ($botfactor < 1.0) { + $xmlWriter->startElement('style:header-footer-properties'); + $xmlWriter->writeAttribute('fo:min-height', $mbottom); + $xmlWriter->writeAttribute('fo:margin-top', $mbottom); + $xmlWriter->writeAttribute('style:dynamic-spacing', 'true'); + $xmlWriter->endElement(); // style:header-footer-properties + } $xmlWriter->endElement(); // style:footer-style $xmlWriter->endElement(); // style:page-layout @@ -182,19 +249,51 @@ private function writePageLayout(XMLWriter $xmlWriter) /** * Write master style. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @return void */ - private function writeMaster(XMLWriter $xmlWriter) + private function writeMaster(XMLWriter $xmlWriter): void { $xmlWriter->startElement('office:master-styles'); - $xmlWriter->startElement('style:master-page'); - $xmlWriter->writeAttribute('style:name', 'Standard'); - $xmlWriter->writeAttribute('style:page-layout-name', 'Mpm1'); - $xmlWriter->endElement(); // style:master-page + $sections = $this->getParentWriter()->getPhpWord()->getSections(); + $countsects = count($sections); + for ($i = 0; $i < $countsects; ++$i) { + $iplus1 = $i + 1; + $xmlWriter->startElement('style:master-page'); + $xmlWriter->writeAttribute('style:name', "Standard$iplus1"); + $xmlWriter->writeAttribute('style:page-layout-name', "Mpm$iplus1"); + // Multiple headers and footers probably not supported, + // and, even if they are, I'm not sure how, + // so quit after generating one. + foreach ($sections[$i]->getHeaders() as $hdr) { + $xmlWriter->startElement('style:header'); + foreach ($hdr->getElements() as $elem) { + $cl1 = get_class($elem); + $cl2 = str_replace('\\Element\\', '\\Writer\\ODText\\Element\\', $cl1); + if (class_exists($cl2)) { + $wtr = new $cl2($xmlWriter, $elem); + $wtr->write(); + } + } + $xmlWriter->endElement(); // style:header + break; + } + foreach ($sections[$i]->getFooters() as $hdr) { + $xmlWriter->startElement('style:footer'); + foreach ($hdr->getElements() as $elem) { + $cl1 = get_class($elem); + $cl2 = str_replace('\\Element\\', '\\Writer\\ODText\\Element\\', $cl1); + if (class_exists($cl2)) { + $wtr = new $cl2($xmlWriter, $elem); + $wtr->write(); + } + } + $xmlWriter->endElement(); // style:footer + + break; + } + $xmlWriter->endElement(); // style:master-page + } $xmlWriter->endElement(); // office:master-styles } } diff --git a/src/PhpWord/Writer/ODText/Style/AbstractStyle.php b/src/PhpWord/Writer/ODText/Style/AbstractStyle.php index 18d6ce1071..3545009f6b 100644 --- a/src/PhpWord/Writer/ODText/Style/AbstractStyle.php +++ b/src/PhpWord/Writer/ODText/Style/AbstractStyle.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Font) { @@ -37,6 +36,14 @@ public function write() } $xmlWriter = $this->getXmlWriter(); + $stylep = $style->getParagraph(); + if ($stylep instanceof \PhpOffice\PhpWord\Style\Paragraph) { + $temp1 = clone $stylep; + $temp1->setStyleName($style->getStyleName()); + $temp2 = new Paragraph($xmlWriter, $temp1); + $temp2->write(); + } + $xmlWriter->startElement('style:style'); $xmlWriter->writeAttribute('style:name', $style->getStyleName()); $xmlWriter->writeAttribute('style:family', 'text'); @@ -55,7 +62,7 @@ public function write() // Color $color = $style->getColor(); - $xmlWriter->writeAttributeIf($color != '', 'fo:color', '#' . $color); + $xmlWriter->writeAttributeIf($color != '', 'fo:color', '#' . \PhpOffice\PhpWord\Shared\Converter::stringToRgb($color)); // Bold & italic $xmlWriter->writeAttributeIf($style->isBold(), 'fo:font-weight', 'bold'); @@ -77,10 +84,22 @@ public function write() $xmlWriter->writeAttributeIf($style->isSmallCaps(), 'fo:font-variant', 'small-caps'); $xmlWriter->writeAttributeIf($style->isAllCaps(), 'fo:text-transform', 'uppercase'); + //Hidden text + $xmlWriter->writeAttributeIf($style->isHidden(), 'text:display', 'none'); + // Superscript/subscript $xmlWriter->writeAttributeIf($style->isSuperScript(), 'style:text-position', 'super'); $xmlWriter->writeAttributeIf($style->isSubScript(), 'style:text-position', 'sub'); + if ($style->isNoProof()) { + $xmlWriter->writeAttribute('fo:language', 'zxx'); + $xmlWriter->writeAttribute('style:language-asian', 'zxx'); + $xmlWriter->writeAttribute('style:language-complex', 'zxx'); + $xmlWriter->writeAttribute('fo:country', 'none'); + $xmlWriter->writeAttribute('style:country-asian', 'none'); + $xmlWriter->writeAttribute('style:country-complex', 'none'); + } + // @todo Foreground-Color // @todo Background color diff --git a/src/PhpWord/Writer/ODText/Style/Image.php b/src/PhpWord/Writer/ODText/Style/Image.php index 21b9c4ee93..56c4f57a5b 100644 --- a/src/PhpWord/Writer/ODText/Style/Image.php +++ b/src/PhpWord/Writer/ODText/Style/Image.php @@ -1,4 +1,5 @@ getStyle(); diff --git a/src/PhpWord/Writer/ODText/Style/Numbering.php b/src/PhpWord/Writer/ODText/Style/Numbering.php new file mode 100644 index 0000000000..287b25b34a --- /dev/null +++ b/src/PhpWord/Writer/ODText/Style/Numbering.php @@ -0,0 +1,87 @@ +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 03e605a196..ca22a0934c 100644 --- a/src/PhpWord/Writer/ODText/Style/Paragraph.php +++ b/src/PhpWord/Writer/ODText/Style/Paragraph.php @@ -1,4 +1,5 @@ Jc::LEFT, + Jc::START => Jc::RIGHT, + ]; + + private const NON_BIDI_MAP = [ + Jc::START => Jc::LEFT, + Jc::END => Jc::RIGHT, + ]; + /** * Write style. - * - * @return void */ - public function write() + public function write(): void { $style = $this->getStyle(); - if (!$style instanceof \PhpOffice\PhpWord\Style\Paragraph) { + if (!$style instanceof Style\Paragraph) { return; } $xmlWriter = $this->getXmlWriter(); - $marginTop = is_null($style->getSpaceBefore()) ? '0' : round(17.6 / $style->getSpaceBefore(), 2); - $marginBottom = is_null($style->getSpaceAfter()) ? '0' : round(17.6 / $style->getSpaceAfter(), 2); + $marginTop = $style->getSpaceBefore(); + $marginBottom = $style->getSpaceAfter(); $xmlWriter->startElement('style:style'); + + $styleName = (string) $style->getStyleName(); + $styleAuto = false; + $mpm = ''; + $psm = ''; + $pagestart = -1; + $breakafter = $breakbefore = $breakauto = false; + if ($style->isAuto()) { + if (substr($styleName, 0, 2) === 'PB') { + $styleAuto = true; + $breakafter = true; + } elseif (substr($styleName, 0, 2) === 'SB') { + $styleAuto = true; + $mpm = 'Standard' . substr($styleName, 2); + $psn = $style->getNumLevel(); + $pagestart = $psn; + } elseif (substr($styleName, 0, 2) === 'HD') { + $styleAuto = true; + $psm = 'Heading_' . substr($styleName, 2); + $stylep = Style::getStyle($psm); + if ($stylep instanceof Style\Font) { + $stylep = $stylep->getParagraph(); + } + if ($stylep instanceof Style\Paragraph) { + if ($stylep->hasPageBreakBefore()) { + $breakbefore = true; + } + } + } elseif (substr($styleName, 0, 2) === 'HE') { + $styleAuto = true; + $psm = 'Heading_' . substr($styleName, 2); + $breakauto = true; + } else { + $styleAuto = true; + $psm = 'Normal'; + if (preg_match('/^P\\d+_(\\w+)$/', $styleName, $matches)) { + $psm = $matches[1]; + } + } + } + $xmlWriter->writeAttribute('style:name', $style->getStyleName()); $xmlWriter->writeAttribute('style:family', 'paragraph'); - if ($style->isAuto()) { - $xmlWriter->writeAttribute('style:parent-style-name', 'Standard'); - $xmlWriter->writeAttribute('style:master-page-name', 'Standard'); + if ($styleAuto) { + $xmlWriter->writeAttributeIf($psm !== '', 'style:parent-style-name', $psm); + $xmlWriter->writeAttributeIf($mpm !== '', 'style:master-page-name', $mpm); } $xmlWriter->startElement('style:paragraph-properties'); - if ($style->isAuto()) { - $xmlWriter->writeAttribute('style:page-number', 'auto'); - } else { - $xmlWriter->writeAttribute('fo:margin-top', $marginTop . 'cm'); - $xmlWriter->writeAttribute('fo:margin-bottom', $marginBottom . 'cm'); - $xmlWriter->writeAttribute('fo:text-align', $style->getAlign()); + if ($styleAuto) { + if ($breakafter) { + $xmlWriter->writeAttribute('fo:break-after', 'page'); + $xmlWriter->writeAttribute('fo:margin-top', '0cm'); + $xmlWriter->writeAttribute('fo:margin-bottom', '0cm'); + } elseif ($breakbefore) { + $xmlWriter->writeAttribute('fo:break-before', 'page'); + } elseif ($breakauto) { + $xmlWriter->writeAttribute('fo:break-before', 'auto'); + } + if ($pagestart > 0) { + $xmlWriter->writeAttribute('style:page-number', $pagestart); + } } + if (!$breakafter && !$breakbefore && !$breakauto) { + $twipToPoint = Converter::INCH_TO_TWIP / Converter::INCH_TO_POINT; // 20 + $xmlWriter->writeAttributeIf($marginTop !== null, 'fo:margin-top', ($marginTop / $twipToPoint) . 'pt'); + $xmlWriter->writeAttributeIf($marginBottom !== null, 'fo:margin-bottom', ($marginBottom / $twipToPoint) . 'pt'); + } + $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'); + + $tabs = $style->getTabs(); + if ($tabs !== null && count($tabs) > 0) { + $xmlWriter->startElement('style:tab-stops'); + foreach ($tabs as $tab) { + $xmlWriter->startElement('style:tab-stop'); + $xmlWriter->writeAttribute('style:type', $tab->getType()); + $xmlWriter->writeAttribute('style:position', (string) ($tab->getPosition() / Converter::INCH_TO_TWIP) . 'in'); + $xmlWriter->endElement(); + } + $xmlWriter->endElement(); + } + + //Right to left + $xmlWriter->writeAttributeIf($style->isBidi(), 'style:writing-mode', 'rl-tb'); + + //Indentation + $indent = $style->getIndentation(); + //if ($indent instanceof \PhpOffice\PhpWord\Style\Indentation) { + if (!empty($indent)) { + $marg = $indent->getLeft(); + $xmlWriter->writeAttributeIf($marg !== null, 'fo:margin-left', (string) ($marg / Converter::INCH_TO_TWIP) . 'in'); + $marg = $indent->getRight(); + $xmlWriter->writeAttributeIf($marg !== null, 'fo:margin-right', (string) ($marg / Converter::INCH_TO_TWIP) . 'in'); + } + $xmlWriter->endElement(); //style:paragraph-properties + if ($styleAuto && substr($styleName, 0, 2) === 'SB') { + $xmlWriter->startElement('style:text-properties'); + $xmlWriter->writeAttribute('text:display', 'none'); + $xmlWriter->endElement(); + } + $xmlWriter->endElement(); //style:style } } diff --git a/src/PhpWord/Writer/ODText/Style/Section.php b/src/PhpWord/Writer/ODText/Style/Section.php index fa432856c7..05152a3944 100644 --- a/src/PhpWord/Writer/ODText/Style/Section.php +++ b/src/PhpWord/Writer/ODText/Style/Section.php @@ -1,4 +1,5 @@ getStyle(); @@ -40,7 +39,7 @@ public function write() $xmlWriter->startElement('style:style'); $xmlWriter->writeAttribute('style:name', $style->getStyleName()); - $xmlWriter->writeAttribute('style:family', "section"); + $xmlWriter->writeAttribute('style:family', 'section'); $xmlWriter->startElement('style:section-properties'); $xmlWriter->startElement('style:columns'); diff --git a/src/PhpWord/Writer/ODText/Style/Table.php b/src/PhpWord/Writer/ODText/Style/Table.php index dbfb94ed84..b30389d8e8 100644 --- a/src/PhpWord/Writer/ODText/Style/Table.php +++ b/src/PhpWord/Writer/ODText/Style/Table.php @@ -1,4 +1,5 @@ getStyle(); @@ -45,7 +44,22 @@ public function write() //$xmlWriter->writeAttribute('style:width', 'table'); $xmlWriter->writeAttribute('style:rel-width', 100); $xmlWriter->writeAttribute('table:align', 'center'); + $xmlWriter->writeAttributeIf($style->isBidiVisual(), 'style:writing-mode', 'rl-tb'); $xmlWriter->endElement(); // style:table-properties $xmlWriter->endElement(); // style:style + + $cellWidths = $style->getColumnWidths(); + $countCellWidths = $cellWidths === null ? 0 : count($cellWidths); + + for ($i = 0; $i < $countCellWidths; ++$i) { + $width = $cellWidths[$i]; + $xmlWriter->startElement('style:style'); + $xmlWriter->writeAttribute('style:name', $style->getStyleName() . '.' . $i); + $xmlWriter->writeAttribute('style:family', 'table-column'); + $xmlWriter->startElement('style:table-column-properties'); + $xmlWriter->writeAttribute('style:column-width', number_format($width * 0.0017638889, 2, '.', '') . 'cm'); + $xmlWriter->endElement(); // style:table-column-properties + $xmlWriter->endElement(); // style:style + } } } diff --git a/src/PhpWord/Writer/PDF.php b/src/PhpWord/Writer/PDF.php index 178655633c..4f7f1be99a 100644 --- a/src/PhpWord/Writer/PDF.php +++ b/src/PhpWord/Writer/PDF.php @@ -1,4 +1,5 @@ renderer = new $rendererName($phpWord); } @@ -64,6 +63,7 @@ public function __construct(PhpWord $phpWord) * * @param string $name Renderer library method name * @param mixed[] $arguments Array of arguments to pass to the renderer method + * * @return mixed Returned data from the PDF renderer wrapper method */ public function __call($name, $arguments) @@ -73,6 +73,16 @@ public function __call($name, $arguments) // throw new Exception("PDF Rendering library has not been defined."); // } - return call_user_func_array(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 572e4b1dfd..125bf4fa43 100644 --- a/src/PhpWord/Writer/PDF/AbstractRenderer.php +++ b/src/PhpWord/Writer/PDF/AbstractRenderer.php @@ -1,4 +1,5 @@ 'A4', // (210 mm by 297 mm) - ); + ]; /** - * Create new instance + * Create new instance. * * @param PhpWord $phpWord PhpWord object - * @throws \PhpOffice\PhpWord\Exception\Exception */ public function __construct(PhpWord $phpWord) { parent::__construct($phpWord); - $includeFile = Settings::getPdfRendererPath() . '/' . $this->includeFile; - if (file_exists($includeFile)) { - /** @noinspection PhpIncludeInspection Dynamic includes */ - require_once $includeFile; - } else { - // @codeCoverageIgnoreStart - // Can't find any test case. Uncomment when found. - throw new Exception('Unable to load PDF Rendering library'); - // @codeCoverageIgnoreEnd + $this->isPdf = true; + if ($this->includeFile != null) { + $includeFile = Settings::getPdfRendererPath() . '/' . $this->includeFile; + if (file_exists($includeFile)) { + /** @noinspection PhpIncludeInspection Dynamic includes */ + require_once $includeFile; + } else { + // @codeCoverageIgnoreStart + // Can't find any test case. Uncomment when found. + throw new Exception('Unable to load PDF Rendering library'); + // @codeCoverageIgnoreEnd + } + } + + // Configuration + $options = Settings::getPdfRendererOptions(); + if (!empty($options['font'])) { + $this->setFont($options['font']); } } /** - * Get Font + * Get Font. * * @return string */ @@ -109,9 +118,10 @@ public function getFont() * 'arialunicid0-chinese-simplified' * 'arialunicid0-chinese-traditional' * 'arialunicid0-korean' - * 'arialunicid0-japanese' + * 'arialunicid0-japanese'. * * @param string $fontName + * * @return self */ public function setFont($fontName) @@ -122,7 +132,7 @@ public function setFont($fontName) } /** - * Get Paper Size + * Get Paper Size. * * @return int */ @@ -132,19 +142,21 @@ public function getPaperSize() } /** - * Set Paper Size + * Set Paper Size. * * @param int $value Paper size = PAPERSIZE_A4 + * * @return self */ public function setPaperSize($value = 9) { $this->paperSize = $value; + return $this; } /** - * Get Orientation + * Get Orientation. * * @return string */ @@ -154,46 +166,45 @@ public function getOrientation() } /** - * Set Orientation + * Set Orientation. * * @param string $value Page orientation ORIENTATION_DEFAULT + * * @return self */ public function setOrientation($value = 'default') { $this->orientation = $value; + return $this; } /** - * Save PhpWord to PDF file, pre-save + * Save PhpWord to PDF file, pre-save. * * @param string $filename Name of the file to save as + * * @return resource - * @throws \PhpOffice\PhpWord\Exception\Exception */ protected function prepareForSave($filename = null) { - $fileHandle = fopen($filename, 'w'); + $fileHandle = fopen($filename, 'wb'); // @codeCoverageIgnoreStart // Can't find any test case. Uncomment when found. if ($fileHandle === false) { throw new Exception("Could not open file $filename for writing."); } // @codeCoverageIgnoreEnd - $this->isPdf = true; return $fileHandle; } /** - * Save PhpWord to PDF file, post-save + * Save PhpWord to PDF file, post-save. * * @param resource $fileHandle - * @return void - * @throws Exception */ - protected function restoreStateAfterSave($fileHandle) + protected function restoreStateAfterSave($fileHandle): void { fclose($fileHandle); } diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 47054c4a48..464dbfa59f 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -1,4 +1,5 @@ getFont()) { + $options->set('defaultFont', $this->getFont()); + } + + return new DompdfLib($options); + } + + /** + * Save PhpWord to file. */ - public function save($filename = null) + public function save(string $filename): void { $fileHandle = parent::prepareForSave($filename); @@ -49,9 +64,9 @@ public function save($filename = null) $orientation = 'portrait'; // Create PDF - $pdf = new \DOMPDF(); - $pdf->set_paper(strtolower($paperSize), $orientation); - $pdf->load_html($this->getContent()); + $pdf = $this->createExternalWriterInstance(); + $pdf->setPaper(strtolower($paperSize), $orientation); + $pdf->loadHtml(str_replace(PHP_EOL, '', $this->getContent())); $pdf->render(); // Write to file diff --git a/src/PhpWord/Writer/PDF/MPDF.php b/src/PhpWord/Writer/PDF/MPDF.php index 46d456d4f7..03ef1f3ad7 100644 --- a/src/PhpWord/Writer/PDF/MPDF.php +++ b/src/PhpWord/Writer/PDF/MPDF.php @@ -1,4 +1,5 @@ '; + private const BODY_TAG = ''; + /** - * Name of renderer include file + * Overridden to set the correct includefile, only needed for MPDF 5. * - * @var string + * @codeCoverageIgnore */ - protected $includeFile = 'mpdf.php'; + public function __construct(PhpWord $phpWord) + { + if (file_exists(Settings::getPdfRendererPath() . '/mpdf.php')) { + // MPDF version 5.* needs this file to be included, later versions not + $this->includeFile = 'mpdf.php'; + } + parent::__construct($phpWord); + } /** - * Save PhpWord to file. + * Gets the implementation of external PDF library that should be used. * - * @param string $filename Name of the file to save as - * @return void + * @return \Mpdf\Mpdf implementation + */ + protected function createExternalWriterInstance() + { + $mPdfClass = $this->getMPdfClassName(); + + $options = []; + if ($this->getFont()) { + $options['default_font'] = $this->getFont(); + } + + return new $mPdfClass($options); + } + + /** + * Save PhpWord to file. */ - public function save($filename = null) + public function save(string $filename): void { $fileHandle = parent::prepareForSave($filename); @@ -49,7 +76,7 @@ public function save($filename = null) $orientation = strtoupper('portrait'); // Create PDF - $pdf = new \mpdf(); + $pdf = $this->createExternalWriterInstance(); $pdf->_setPageSize($paperSize, $orientation); $pdf->addPage($orientation); @@ -62,11 +89,46 @@ public function save($filename = null) $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')); parent::restoreStateAfterSave($fileHandle); } + + /** + * Return classname of MPDF to instantiate. + * + * @codeCoverageIgnore + * + * @return string + */ + private function getMPdfClassName() + { + if ($this->includeFile != null) { + // MPDF version 5.* + return '\mpdf'; + } + + // MPDF version > 6.* + return '\Mpdf\Mpdf'; + } } diff --git a/src/PhpWord/Writer/PDF/TCPDF.php b/src/PhpWord/Writer/PDF/TCPDF.php index 36849e24ad..93bdd58528 100644 --- a/src/PhpWord/Writer/PDF/TCPDF.php +++ b/src/PhpWord/Writer/PDF/TCPDF.php @@ -1,4 +1,5 @@ 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. */ - public function save($filename = null) + public function save(string $filename): void { $fileHandle = parent::prepareForSave($filename); // PDF settings - $paperSize = 'A4'; + $paperSize = strtoupper(Settings::getDefaultPaper()); $orientation = 'P'; // Create PDF - $pdf = new \TCPDF($orientation, 'pt', $paperSize); + $pdf = $this->createExternalWriterInstance($orientation, 'pt', $paperSize); $pdf->setFontSubsetting(false); $pdf->setPrintHeader(false); $pdf->setPrintFooter(false); - $pdf->addPage(); - $pdf->setFont($this->getFont()); + $pdf->SetFont($this->getFont()); + $this->prepareToWrite($pdf); $pdf->writeHTML($this->getContent()); // Write document properties $phpWord = $this->getPhpWord(); $docProps = $phpWord->getDocInfo(); - $pdf->setTitle($docProps->getTitle()); - $pdf->setAuthor($docProps->getCreator()); - $pdf->setSubject($docProps->getSubject()); - $pdf->setKeywords($docProps->getKeywords()); - $pdf->setCreator($docProps->getCreator()); + $pdf->SetTitle($docProps->getTitle()); + $pdf->SetAuthor($docProps->getCreator()); + $pdf->SetSubject($docProps->getSubject()); + $pdf->SetKeywords($docProps->getKeywords()); + $pdf->SetCreator($docProps->getCreator()); // Write to file - fwrite($fileHandle, $pdf->output($filename, 'S')); + fwrite($fileHandle, $pdf->Output($filename, 'S')); parent::restoreStateAfterSave($fileHandle); } diff --git a/src/PhpWord/Writer/RTF.php b/src/PhpWord/Writer/RTF.php index 58210c997f..390311aa26 100644 --- a/src/PhpWord/Writer/RTF.php +++ b/src/PhpWord/Writer/RTF.php @@ -1,4 +1,5 @@ setPhpWord($phpWord); - $this->parts = array('Header', 'Document'); + $this->parts = ['Header', 'Document']; foreach ($this->parts as $partName) { - $partClass = get_class($this) . '\\Part\\' . $partName; + $partClass = static::class . '\\Part\\' . $partName; if (class_exists($partClass)) { - /** @var \PhpOffice\PhpWord\Writer\RTF\Part\AbstractPart $part Type hint */ + /** @var RTF\Part\AbstractPart $part Type hint */ $part = new $partClass(); $part->setParentWriter($this); $this->writerParts[strtolower($partName)] = $part; @@ -56,20 +55,17 @@ public function __construct(PhpWord $phpWord = null) /** * Save content to file. - * - * @param string $filename - * @return void - * @throws \PhpOffice\PhpWord\Exception\Exception */ - public function save($filename = null) + public function save(string $filename): void { $this->writeFile($this->openFile($filename), $this->getContent()); } /** - * Get content + * Get content. * * @return string + * * @since 0.11.0 */ private function getContent() @@ -119,9 +115,8 @@ public function getLastParagraphStyle() * Set last paragraph style. * * @param mixed $value - * @return void */ - public function setLastParagraphStyle($value = '') + public function setLastParagraphStyle($value = ''): void { $this->lastParagraphStyle = $value; } diff --git a/src/PhpWord/Writer/RTF/Element/AbstractElement.php b/src/PhpWord/Writer/RTF/Element/AbstractElement.php index 73da5cbddb..e007e6aa26 100644 --- a/src/PhpWord/Writer/RTF/Element/AbstractElement.php +++ b/src/PhpWord/Writer/RTF/Element/AbstractElement.php @@ -1,4 +1,5 @@ parentWriter = $parentWriter; + $this->element = $element; + $this->withoutP = $withoutP; + $this->escaper = new Rtf(); + } + + /** + * Get font and paragraph styles. */ - protected function getStyles() + protected function getStyles(): void { - /** @var \PhpOffice\PhpWord\Writer\RTF $parentWriter Type hint */ + /** @var WriterRTF $parentWriter Type hint */ $parentWriter = $this->parentWriter; /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ @@ -89,7 +132,7 @@ protected function getStyles() } /** - * Write opening + * Write opening. * * @return string */ @@ -101,22 +144,28 @@ protected function writeOpening() $styleWriter = new ParagraphStyleWriter($this->paragraphStyle); $styleWriter->setNestedLevel($this->element->getNestedLevel()); + return $styleWriter->write(); } /** - * Write text + * Write text. * * @param string $text + * * @return string */ protected function writeText($text) { - return String::toUnicode($text); + if (Settings::isOutputEscapingEnabled()) { + return $this->escaper->escape($text); + } + + return SharedText::toUnicode($text); // todo: replace with `return $text;` later. } /** - * Write closing + * Write closing. * * @return string */ @@ -130,7 +179,7 @@ protected function writeClosing() } /** - * Write font style + * Write font style. * * @return string */ @@ -140,7 +189,7 @@ protected function writeFontStyle() return ''; } - /** @var \PhpOffice\PhpWord\Writer\RTF $parentWriter Type hint */ + /** @var WriterRTF $parentWriter Type hint */ $parentWriter = $this->parentWriter; // Create style writer and set color/name index diff --git a/src/PhpWord/Writer/RTF/Element/Container.php b/src/PhpWord/Writer/RTF/Element/Container.php index cb95b84bd2..dcac8ec071 100644 --- a/src/PhpWord/Writer/RTF/Element/Container.php +++ b/src/PhpWord/Writer/RTF/Element/Container.php @@ -1,4 +1,5 @@ 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 new file mode 100644 index 0000000000..c5e49c1d44 --- /dev/null +++ b/src/PhpWord/Writer/RTF/Element/Field.php @@ -0,0 +1,94 @@ +element; + if (!$element instanceof ElementField) { + return; + } + + $this->getStyles(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= '{'; + $content .= $this->writeFontStyle(); + + $methodName = 'write' . ucfirst(strtolower($element->getType())); + if (!method_exists($this, $methodName)) { + // Unsupported field + $content .= ''; + } else { + $content .= '\\field{\\*\\fldinst '; + $content .= $this->$methodName($element); + $content .= '}{\\fldrslt}'; + } + $content .= '}'; + $content .= $this->writeClosing(); + + return $content; + } + + protected function writePage() + { + return 'PAGE'; + } + + protected function writeNumpages() + { + return 'NUMPAGES'; + } + + protected function 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'; + $properties = $element->getProperties(); + if (isset($properties['dateformat'])) { + $content .= ' \\\\@ "' . $properties['dateformat'] . '"'; + } + + return $content; + } +} diff --git a/src/PhpWord/Writer/RTF/Element/Image.php b/src/PhpWord/Writer/RTF/Element/Image.php index 52e705e9e2..53052c85e7 100644 --- a/src/PhpWord/Writer/RTF/Element/Image.php +++ b/src/PhpWord/Writer/RTF/Element/Image.php @@ -1,4 +1,5 @@ element; + $elementClass = str_replace('\\Writer\\RTF', '', static::class); + if (!$element instanceof $elementClass || !is_string($element->getBaseTextRun()->getText())) { + return ''; + } + + $this->getStyles(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= '{'; + $content .= $this->writeFontStyle(); + $content .= $this->writeText($element->getBaseTextRun()->getText()); + $rubyText = $element->getRubyTextRun()->getText(); + if ($rubyText !== '') { + $content .= ' ('; + $content .= $this->writeText($rubyText); + $content .= ')'; + } + $content .= '}'; + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/src/PhpWord/Writer/RTF/Element/Table.php b/src/PhpWord/Writer/RTF/Element/Table.php index 7c4329f719..3b08a5db86 100644 --- a/src/PhpWord/Writer/RTF/Element/Table.php +++ b/src/PhpWord/Writer/RTF/Element/Table.php @@ -1,4 +1,5 @@ element->getStyle(); + $bidiStyle = (is_object($style) && method_exists($style, 'isBidiVisual')) ? $style->isBidiVisual() : Settings::isDefaultRtl(); + $bidi = $bidiStyle ? '\rtlrow' : ''; $rows = $element->getRows(); $rowCount = count($rows); if ($rowCount > 0) { $content .= '\pard' . PHP_EOL; - for ($i = 0; $i < $rowCount; $i++) { - $content .= '\trowd '; + for ($i = 0; $i < $rowCount; ++$i) { + $content .= "\\trowd$bidi "; $content .= $this->writeRowDef($rows[$i]); $content .= PHP_EOL; $content .= $this->writeRow($rows[$i]); $content .= '\row' . PHP_EOL; } + $content .= '\pard' . PHP_EOL; } return $content; } /** - * Write column + * Write column. * - * @param \PhpOffice\PhpWord\Element\Row $row * @return string */ 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) { $width = 720; // Arbitrary default width } $rightMargin += $width; - $content .= "{$vMerge}\cellx{$rightMargin} "; + $content .= "{$vMerge}\\cellx{$rightMargin} "; } return $content; } /** - * Write row + * Write row. * - * @param \PhpOffice\PhpWord\Element\Row $row * @return string */ private function writeRow(RowElement $row) @@ -106,9 +128,8 @@ private function writeRow(RowElement $row) } /** - * Write cell + * Write cell. * - * @param \PhpOffice\PhpWord\Element\Cell $cell * @return string */ private function writeCell(CellElement $cell) @@ -124,11 +145,110 @@ 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 + * Get vertical merge style. * * @param string $value + * * @return string + * * @todo Move to style */ private function getVMerge($value) diff --git a/src/PhpWord/Writer/RTF/Element/Text.php b/src/PhpWord/Writer/RTF/Element/Text.php index 38ef4c948c..71d3a27ce8 100644 --- a/src/PhpWord/Writer/RTF/Element/Text.php +++ b/src/PhpWord/Writer/RTF/Element/Text.php @@ -1,4 +1,5 @@ element; - $elementClass = str_replace('\\Writer\\RTF', '', get_class($this)); - if (!$element instanceof $elementClass) { + $elementClass = str_replace('\\Writer\\RTF', '', static::class); + if (!$element instanceof $elementClass || !is_string($element->getText())) { return ''; } diff --git a/src/PhpWord/Writer/RTF/Element/TextBreak.php b/src/PhpWord/Writer/RTF/Element/TextBreak.php index 57dc634994..d74bf23dcb 100644 --- a/src/PhpWord/Writer/RTF/Element/TextBreak.php +++ b/src/PhpWord/Writer/RTF/Element/TextBreak.php @@ -1,4 +1,5 @@ parentWriter, $this->element); + $this->getStyles(); $content = ''; $content .= $this->writeOpening(); diff --git a/src/PhpWord/Writer/RTF/Element/Title.php b/src/PhpWord/Writer/RTF/Element/Title.php index b9645a6897..06266897b3 100644 --- a/src/PhpWord/Writer/RTF/Element/Title.php +++ b/src/PhpWord/Writer/RTF/Element/Title.php @@ -1,4 +1,5 @@ element; + $style = $element->getStyle(); + $style = str_replace('Heading', 'Heading_', $style ?? ''); + $style = \PhpOffice\PhpWord\Style::getStyle($style); + if ($style instanceof \PhpOffice\PhpWord\Style\Font) { + $this->fontStyle = $style; + $pstyle = $style->getParagraph(); + if ($pstyle instanceof \PhpOffice\PhpWord\Style\Paragraph && $pstyle->hasPageBreakBefore()) { + $sect = $element->getParent(); + if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { + $elems = $sect->getElements(); + if ($elems[0] === $element) { + $pstyle = clone $pstyle; + $pstyle->setPageBreakBefore(false); + } + } + } + $this->paragraphStyle = $pstyle; + } + } + + /** + * Write element. + * + * @return string + */ + public function write() + { + /** @var \PhpOffice\PhpWord\Element\Title $element Type hint */ + $element = $this->element; + $elementClass = str_replace('\\Writer\\RTF', '', static::class); + if (!$element instanceof $elementClass) { + return ''; + } + + $textToWrite = $element->getText(); + if ($textToWrite instanceof \PhpOffice\PhpWord\Element\TextRun) { + $textToWrite = $textToWrite->getText(); // gets text from TextRun + } + + $this->getStyles(); + + $content = ''; + + $content .= $this->writeOpening(); + $endout = ''; + $style = $element->getStyle(); + if (is_string($style)) { + $style = str_replace('Heading', '', $style); + if ("$style" !== '') { + $style = (int) $style - 1; + if ($style >= 0 && $style <= 8) { + $content .= '{\\outlinelevel' . $style; + $endout = '}'; + } + } + } + + $content .= '{'; + $content .= $this->writeFontStyle(); + $content .= $this->writeText($textToWrite); + $content .= '}'; + $content .= $this->writeClosing(); + $content .= $endout; + + return $content; + } } diff --git a/src/PhpWord/Writer/RTF/Part/AbstractPart.php b/src/PhpWord/Writer/RTF/Part/AbstractPart.php index b10e5654c7..a07f70bbf7 100644 --- a/src/PhpWord/Writer/RTF/Part/AbstractPart.php +++ b/src/PhpWord/Writer/RTF/Part/AbstractPart.php @@ -1,4 +1,5 @@ escaper = new Rtf(); + } + + /** + * @return string + */ + abstract public function write(); + + /** + * @param \PhpOffice\PhpWord\Writer\RTF $writer + */ + public function setParentWriter(?AbstractWriter $writer = null): void + { + $this->parentWriter = $writer; + } + + /** + * @return \PhpOffice\PhpWord\Writer\RTF + */ + public function getParentWriter() + { + if ($this->parentWriter !== null) { + return $this->parentWriter; + } + + throw new Exception('No parent WriterInterface assigned.'); + } } diff --git a/src/PhpWord/Writer/RTF/Part/Document.php b/src/PhpWord/Writer/RTF/Part/Document.php index edcdbd84a4..484393477d 100644 --- a/src/PhpWord/Writer/RTF/Part/Document.php +++ b/src/PhpWord/Writer/RTF/Part/Document.php @@ -1,4 +1,5 @@ getParentWriter()->getPhpWord()->getDocInfo(); - $properties = array('title', 'subject', 'category', 'keywords', 'comment', - 'author', 'operator', 'creatim', 'revtim', 'company', 'manager'); - $mapping = array('comment' => 'description', 'author' => 'creator', 'operator' => 'lastModifiedBy', - 'creatim' => 'created', 'revtim' => 'modified'); - $dateFields = array('creatim', 'revtim'); + $properties = [ + 'title' => 'title', + 'subject' => 'subject', + 'category' => 'category', + 'keywords' => 'keywords', + 'comment' => 'description', + 'author' => 'creator', + 'operator' => 'lastModifiedBy', + 'creatim' => 'created', + 'revtim' => 'modified', + 'company' => 'company', + 'manager' => 'manager', + ]; + $dateFields = ['creatim', 'revtim']; $content = ''; $content .= '{'; $content .= '\info'; - foreach ($properties as $property) { - $method = 'get' . (isset($mapping[$property]) ? $mapping[$property] : $property); + foreach ($properties as $property => $propertyMethod) { + $method = 'get' . $propertyMethod; + $value = $docProps->$method(); + if (!in_array($property, $dateFields) && Settings::isOutputEscapingEnabled()) { + $value = $this->escaper->escape($value); + } + $value = in_array($property, $dateFields) ? $this->getDateValue($value) : $value; $content .= "{\\{$property} {$value}}"; } @@ -76,12 +92,16 @@ private function writeInfo() } /** - * Write document formatting properties + * Write document formatting properties. * * @return string */ private function writeFormatting() { + $docSettings = $this->getParentWriter()->getPhpWord()->getSettings(); + // Applies a language to a text run (defaults to 1036 : French (France)) + $langId = $docSettings->getThemeFontLang() != null && $docSettings->getThemeFontLang()->getLangId() != null ? $docSettings->getThemeFontLang()->getLangId() : 1036; + $content = ''; $content .= '\deftab720'; // Set the default tab size (720 twips) @@ -90,57 +110,130 @@ private function writeFormatting() $content .= '\uc1'; // Set the numberof bytes that follows a unicode character $content .= '\pard'; // Resets to default paragraph properties. $content .= '\nowidctlpar'; // No widow/orphan control - $content .= '\lang1036'; // Applies a language to a text run (1036 : French (France)) + $content .= '\lang' . $langId; $content .= '\kerning1'; // Point size (in half-points) above which to kern character pairs $content .= '\fs' . (Settings::getDefaultFontSize() * 2); // Set the font size in half-points + if ($docSettings->hasEvenAndOddHeaders()) { + $content .= '\\facingp'; + } $content .= PHP_EOL; return $content; } /** - * Write sections + * Write titlepg directive if any "f" headers or footers. + * + * @param \PhpOffice\PhpWord\Element\Section $section * * @return string */ - private function writeSections() + private static function writeTitlepg($section) { + foreach ($section->getHeaders() as $header) { + if ($header->getType() === Footer::FIRST) { + return '\\titlepg' . PHP_EOL; + } + } + foreach ($section->getFooters() as $header) { + if ($header->getType() === Footer::FIRST) { + return '\\titlepg' . PHP_EOL; + } + } + return ''; + } + + /** + * Write sections. + * + * @return string + */ + private function writeSections() + { $content = ''; $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(); + $content .= self::writeTitlepg($section); + + foreach ($section->getHeaders() as $header) { + $type = $header->getType(); + if ($evenOdd || $type !== Footer::EVEN) { + $content .= '{\\header'; + if ($type === Footer::FIRST) { + $content .= 'f'; + } elseif ($evenOdd) { + $content .= ($type === Footer::EVEN) ? 'l' : 'r'; + } + foreach ($header->getElements() as $element) { + $cl = get_class($element); + $cl2 = str_replace('Element', 'Writer\\RTF\\Element', $cl); + if (class_exists($cl2)) { + $elementWriter = new $cl2($this->getParentWriter(), $element); + $content .= $elementWriter->write(); + } + } + $content .= '}' . PHP_EOL; + } + } + foreach ($section->getFooters() as $footer) { + $type = $footer->getType(); + if ($evenOdd || $type !== Footer::EVEN) { + $content .= '{\\footer'; + if ($type === Footer::FIRST) { + $content .= 'f'; + } elseif ($evenOdd) { + $content .= ($type === Footer::EVEN) ? 'l' : 'r'; + } + foreach ($footer->getElements() as $element) { + $cl = get_class($element); + $cl2 = str_replace('Element', 'Writer\\RTF\\Element', $cl); + if (class_exists($cl2)) { + $elementWriter = new $cl2($this->getParentWriter(), $element); + $content .= $elementWriter->write(); + } + } + $content .= '}' . PHP_EOL; + } + } $elementWriter = new Container($this->getParentWriter(), $section); $content .= $elementWriter->write(); - - $content .= '\sect' . PHP_EOL; } return $content; } /** - * Get date value + * Get date value. * * The format of date value is `\yr?\mo?\dy?\hr?\min?\sec?` * * @param int $value + * * @return string */ private function getDateValue($value) { - $dateParts = array( + $dateParts = [ 'Y' => 'yr', 'm' => 'mo', 'd' => 'dy', 'H' => 'hr', 'i' => 'min', 's' => 'sec', - ); + ]; $result = ''; foreach ($dateParts as $dateFormat => $controlWord) { $result .= '\\' . $controlWord . date($dateFormat, $value); diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 31fbb7f4af..97644fe4ac 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -1,4 +1,5 @@ colorTable as $color) { - list($red, $green, $blue) = Converter::htmlToRgb($color); + [$red, $green, $blue] = Converter::htmlToRgb($color); $content .= "\\red{$red}\\green{$green}\\blue{$blue};"; } $content .= '}'; @@ -165,7 +167,7 @@ private function writeColorTable() } /** - * Write + * Write. * * @return string */ @@ -173,7 +175,7 @@ private function writeGenerator() { $content = ''; - $content .= '{\*\generator PhpWord;}'; // Set the generator + $content .= '{\*\generator PHPWord;}'; // Set the generator $content .= PHP_EOL; return $content; @@ -181,10 +183,8 @@ private function writeGenerator() /** * Register all fonts and colors in both named and inline styles to appropriate header table. - * - * @return void */ - private function registerFont() + private function registerFont(): void { $phpWord = $this->getParentWriter()->getPhpWord(); $this->fontTable[] = Settings::getDefaultFontName(); @@ -212,10 +212,9 @@ private function registerFont() /** * Register border colors. * - * @param \PhpOffice\PhpWord\Style\Border $style - * @return void + * @param Style\Border $style */ - private function registerBorderColor($style) + private function registerBorderColor($style): void { $colors = $style->getBorderColor(); foreach ($colors as $color) { @@ -228,10 +227,9 @@ private function registerBorderColor($style) /** * Register fonts and colors. * - * @param \PhpOffice\PhpWord\Style\AbstractStyle $style - * @return void + * @param Style\AbstractStyle $style */ - private function registerFontItems($style) + private function registerFontItems($style): void { $defaultFont = Settings::getDefaultFontName(); $defaultColor = Settings::DEFAULT_FONT_COLOR; @@ -240,6 +238,14 @@ private function registerFontItems($style) $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); } } @@ -249,9 +255,8 @@ private function registerFontItems($style) * @param array &$table * @param string $value * @param string $default - * @return void */ - private function registerTableItem(&$table, $value, $default = null) + private function registerTableItem(&$table, $value, $default = null): void { if (in_array($value, $table) === false && $value !== null && $value != $default) { $table[] = $value; diff --git a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php index 7b55fdc2f9..00e148dfe9 100644 --- a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php +++ b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php @@ -1,4 +1,5 @@ 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/Border.php b/src/PhpWord/Writer/RTF/Style/Border.php index 88c517ad79..c8dc943579 100644 --- a/src/PhpWord/Writer/RTF/Style/Border.php +++ b/src/PhpWord/Writer/RTF/Style/Border.php @@ -1,4 +1,5 @@ sizes) - 1; + $sides = ['top', 'left', 'right', 'bottom']; + $sizeCount = count($this->sizes); // Page border measure // 8 = from text, infront off; 32 = from edge, infront on; 40 = from edge, infront off $content .= '\pgbrdropt32'; - for ($i = 0; $i < $sizeCount; $i++) { + for ($i = 0; $i < $sizeCount; ++$i) { if ($this->sizes[$i] !== null) { $color = null; if (isset($this->colors[$i])) { @@ -68,11 +69,12 @@ public function write() } /** - * Write side + * Write side. * * @param string $side * @param int $width * @param string $color + * * @return string */ private function writeSide($side, $width, $color = '') @@ -83,7 +85,7 @@ private function writeSide($side, $width, $color = '') if ($rtfWriter !== null) { $colorTable = $rtfWriter->getColorTable(); $index = array_search($color, $colorTable); - if ($index !== false && $colorIndex !== null) { + if ($index !== false) { $colorIndex = $index + 1; } } @@ -92,7 +94,7 @@ private function writeSide($side, $width, $color = '') $content .= '\pgbrdr' . substr($side, 0, 1); $content .= '\brdrs'; // Single-thickness border; @todo Get other type of border - $content .= '\brdrw' . $width; // Width + $content .= '\brdrw' . round($width); // Width $content .= '\brdrcf' . $colorIndex; // Color $content .= '\brsp480'; // Space in twips between borders and the paragraph (24pt, following OOXML) $content .= ' '; @@ -103,10 +105,9 @@ private function writeSide($side, $width, $color = '') /** * Set sizes. * - * @param integer[] $value - * @return void + * @param int[] $value */ - public function setSizes($value) + public function setSizes($value): void { $this->sizes = $value; } @@ -115,9 +116,8 @@ public function setSizes($value) * Set colors. * * @param string[] $value - * @return void */ - public function setColors($value) + public function setColors($value): void { $this->colors = $value; } diff --git a/src/PhpWord/Writer/RTF/Style/Font.php b/src/PhpWord/Writer/RTF/Style/Font.php index 20c47aee08..f343c0502f 100644 --- a/src/PhpWord/Writer/RTF/Style/Font.php +++ b/src/PhpWord/Writer/RTF/Style/Font.php @@ -1,4 +1,5 @@ getValueIf($style->isRTL(), '\rtlch'); $content .= '\cf' . $this->colorIndex; $content .= '\f' . $this->nameIndex; $size = $style->getSize(); - $content .= $this->getValueIf(is_numeric($size), '\fs' . ($size * 2)); + $content .= $this->getValueIf(is_numeric($size), '\fs' . round($size * 2)); $content .= $this->getValueIf($style->isBold(), '\b'); $content .= $this->getValueIf($style->isItalic(), '\i'); @@ -62,17 +64,15 @@ public function write() $content .= $this->getValueIf($style->isSuperScript(), '\super'); $content .= $this->getValueIf($style->isSubScript(), '\sub'); - return $content . ' '; + return $content . ' '; } /** * Set font name index. * - * * @param int $value - * @return void */ - public function setNameIndex($value = 0) + public function setNameIndex($value = 0): void { $this->nameIndex = $value; } @@ -81,9 +81,8 @@ public function setNameIndex($value = 0) * Set font color index. * * @param int $value - * @return void */ - public function setColorIndex($value = 0) + public function setColorIndex($value = 0): void { $this->colorIndex = $value; } diff --git a/src/PhpWord/Writer/Word2007/Style/Alignment.php b/src/PhpWord/Writer/RTF/Style/Indentation.php similarity index 55% rename from src/PhpWord/Writer/Word2007/Style/Alignment.php rename to src/PhpWord/Writer/RTF/Style/Indentation.php index fabaf8eac4..589125a26a 100644 --- a/src/PhpWord/Writer/Word2007/Style/Alignment.php +++ b/src/PhpWord/Writer/RTF/Style/Indentation.php @@ -1,4 +1,5 @@ getStyle(); - if (!$style instanceof \PhpOffice\PhpWord\Style\Alignment) { - return; - } - $value = $style->getValue(); - if ($value !== null) { - $xmlWriter = $this->getXmlWriter(); - $xmlWriter->startElement('w:jc'); - $xmlWriter->writeAttribute('w:val', $value); - $xmlWriter->endElement(); // w:jc + if (!$style instanceof \PhpOffice\PhpWord\Style\Indentation) { + return ''; } + + $content = '\fi' . round($style->getFirstLine()); + $content .= '\li' . round($style->getLeft()); + $content .= '\ri' . round($style->getRight()); + + return $content . ' '; } } diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index 1a7de0a35b..040c60b5aa 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -1,4 +1,5 @@ '\ql', - Alignment::ALIGN_RIGHT => '\qr', - Alignment::ALIGN_CENTER => '\qc', - Alignment::ALIGN_BOTH => '\qj', - ); + $alignments = [ + Jc::START => '\ql', + 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', + ]; - $align = $style->getAlign(); $spaceAfter = $style->getSpaceAfter(); $spaceBefore = $style->getSpaceBefore(); @@ -63,11 +78,66 @@ public function write() if ($this->nestedLevel == 0) { $content .= '\pard\nowidctlpar '; } - if (isset($alignments[$align])) { - $content .= $alignments[$align]; + $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)); + $content .= $this->getValueIf($spaceAfter !== null, '\sa' . round($spaceAfter ?? 0)); + $lineHeight = $style->getLineHeight(); + if ($lineHeight) { + $lineHeightAdjusted = (int) ($lineHeight * 240); + $content .= "\\sl$lineHeightAdjusted\\slmult1"; + } + if ($style->hasPageBreakBefore()) { + $content .= '\\page'; + } + + $styles = $style->getStyleValues(); + $content .= $this->writeTabs($styles['tabs']); + + return $content; + } + + /** + * Writes an \PhpOffice\PhpWord\Style\Indentation. + * + * @param null|\PhpOffice\PhpWord\Style\Indentation $indent + * + * @return string + */ + private function writeIndentation($indent = null) + { + if (isset($indent) && $indent instanceof \PhpOffice\PhpWord\Style\Indentation) { + $writer = new Indentation($indent); + + return $writer->write(); + } + + return ''; + } + + /** + * Writes tabs. + * + * @param \PhpOffice\PhpWord\Style\Tab[] $tabs + * + * @return string + */ + private function writeTabs($tabs = null) + { + $content = ''; + if (!empty($tabs)) { + foreach ($tabs as $tab) { + $styleWriter = new Tab($tab); + $content .= $styleWriter->write(); + } } - $content .= $this->getValueIf($spaceBefore !== null, '\sb' . $spaceBefore); - $content .= $this->getValueIf($spaceAfter !== null, '\sa' . $spaceAfter); return $content; } @@ -76,9 +146,8 @@ public function write() * Set nested level. * * @param int $value - * @return void */ - public function setNestedLevel($value) + public function setNestedLevel($value): void { $this->nestedLevel = $value; } diff --git a/src/PhpWord/Writer/RTF/Style/Section.php b/src/PhpWord/Writer/RTF/Style/Section.php index 4169b630cc..598015ed4d 100644 --- a/src/PhpWord/Writer/RTF/Style/Section.php +++ b/src/PhpWord/Writer/RTF/Style/Section.php @@ -1,4 +1,5 @@ getValueIf($style->getPageSizeW() !== null, '\pgwsxn' . $style->getPageSizeW()); - $content .= $this->getValueIf($style->getPageSizeH() !== null, '\pghsxn' . $style->getPageSizeH()); + $content .= $this->getValueIf($style->getPageSizeW() !== null, '\pgwsxn' . round($style->getPageSizeW())); + $content .= $this->getValueIf($style->getPageSizeH() !== null, '\pghsxn' . round($style->getPageSizeH())); $content .= ' '; - $content .= $this->getValueIf($style->getMarginTop() !== null, '\margtsxn' . $style->getMarginTop()); - $content .= $this->getValueIf($style->getMarginRight() !== null, '\margrsxn' . $style->getMarginRight()); - $content .= $this->getValueIf($style->getMarginBottom() !== null, '\margbsxn' . $style->getMarginBottom()); - $content .= $this->getValueIf($style->getMarginLeft() !== null, '\marglsxn' . $style->getMarginLeft()); - $content .= $this->getValueIf($style->getHeaderHeight() !== null, '\headery' . $style->getHeaderHeight()); - $content .= $this->getValueIf($style->getFooterHeight() !== null, '\footery' . $style->getFooterHeight()); - $content .= $this->getValueIf($style->getGutter() !== null, '\guttersxn' . $style->getGutter()); + $content .= $this->getValueIf($style->getMarginTop() !== null, '\margtsxn' . round($style->getMarginTop())); + $content .= $this->getValueIf($style->getMarginRight() !== null, '\margrsxn' . round($style->getMarginRight())); + $content .= $this->getValueIf($style->getMarginBottom() !== null, '\margbsxn' . round($style->getMarginBottom())); + $content .= $this->getValueIf($style->getMarginLeft() !== null, '\marglsxn' . round($style->getMarginLeft())); + $content .= $this->getValueIf($style->getHeaderHeight() !== null, '\headery' . round($style->getHeaderHeight())); + $content .= $this->getValueIf($style->getFooterHeight() !== null, '\footery' . round($style->getFooterHeight())); + $content .= $this->getValueIf($style->getGutter() !== null, '\guttersxn' . round($style->getGutter())); + $content .= $this->getValueIf($style->getPageNumberingStart() !== null, '\pgnstarts' . $style->getPageNumberingStart() . '\pgnrestart'); $content .= ' '; // Borders diff --git a/src/PhpWord/Writer/RTF/Style/Tab.php b/src/PhpWord/Writer/RTF/Style/Tab.php new file mode 100644 index 0000000000..95e1f10a5c --- /dev/null +++ b/src/PhpWord/Writer/RTF/Style/Tab.php @@ -0,0 +1,50 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Tab) { + return; + } + $tabs = [ + \PhpOffice\PhpWord\Style\Tab::TAB_STOP_RIGHT => '\tqr', + \PhpOffice\PhpWord\Style\Tab::TAB_STOP_CENTER => '\tqc', + \PhpOffice\PhpWord\Style\Tab::TAB_STOP_DECIMAL => '\tqdec', + ]; + $content = ''; + if (isset($tabs[$style->getType()])) { + $content .= $tabs[$style->getType()]; + } + $content .= '\tx' . round($style->getPosition()); + + return $content; + } +} diff --git a/src/PhpWord/Writer/Word2007.php b/src/PhpWord/Writer/Word2007.php index 09d095093f..9702c210bd 100644 --- a/src/PhpWord/Writer/Word2007.php +++ b/src/PhpWord/Writer/Word2007.php @@ -1,4 +1,5 @@ array(), 'override' => array()); + private $contentTypes = ['default' => [], 'override' => []]; /** - * Document relationship + * Document relationship. * * @var array */ - private $relationships = array(); + private $relationships = []; /** - * Create new Word2007 writer - * - * @param \PhpOffice\PhpWord\PhpWord + * Create new Word2007 writer. */ - public function __construct(PhpWord $phpWord = null) + public function __construct(?PhpWord $phpWord = null) { // Assign PhpWord $this->setPhpWord($phpWord); // Create parts - $this->parts = array( - 'ContentTypes' => '[Content_Types].xml', - 'Rels' => '_rels/.rels', - 'DocPropsApp' => 'docProps/app.xml', - 'DocPropsCore' => 'docProps/core.xml', + // 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', - 'Styles' => 'word/styles.xml', - 'Numbering' => 'word/numbering.xml', - 'Settings' => 'word/settings.xml', - 'WebSettings' => 'word/webSettings.xml', - 'FontTable' => 'word/fontTable.xml', - 'Theme' => 'word/theme/theme1.xml', - 'RelsPart' => '', - 'Header' => '', - 'Footer' => '', - 'Footnotes' => '', - 'Endnotes' => '', - 'Chart' => '', - ); + 'Comments' => 'word/comments.xml', + 'Styles' => 'word/styles.xml', + 'Numbering' => 'word/numbering.xml', + 'Settings' => 'word/settings.xml', + 'WebSettings' => 'word/webSettings.xml', + 'FontTable' => 'word/fontTable.xml', + 'Theme' => 'word/theme/theme1.xml', + 'RelsPart' => '', + 'Header' => '', + 'Footer' => '', + 'Footnotes' => '', + 'Endnotes' => '', + 'Chart' => '', + ]; foreach (array_keys($this->parts) as $partName) { - $partClass = get_class($this) . '\\Part\\' . $partName; + $partClass = static::class . '\\Part\\' . $partName; if (class_exists($partClass)) { - /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $part Type hint */ + /** @var Word2007\Part\AbstractPart $part Type hint */ $part = new $partClass(); $part->setParentWriter($this); $this->writerParts[strtolower($partName)] = $part; @@ -84,26 +85,23 @@ public function __construct(PhpWord $phpWord = null) } // Set package paths - $this->mediaPaths = array('image' => 'word/media/', 'object' => 'word/embeddings/'); + $this->mediaPaths = ['image' => 'word/media/', 'object' => 'word/embeddings/']; } /** * Save document by name. - * - * @param string $filename - * @return void */ - public function save($filename = null) + public function save(string $filename): void { $filename = $this->getTempFile($filename); $zip = $this->getZipArchive($filename); $phpWord = $this->getPhpWord(); // Content types - $this->contentTypes['default'] = array( + $this->contentTypes['default'] = [ 'rels' => 'application/vnd.openxmlformats-package.relationships+xml', - 'xml' => 'application/xml', - ); + 'xml' => 'application/xml', + ]; // Add section media files $sectionMedia = Media::getElements('section'); @@ -120,7 +118,7 @@ public function save($filename = null) $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); @@ -129,6 +127,7 @@ public function save($filename = null) $this->addNotes($zip, $rId, 'footnote'); $this->addNotes($zip, $rId, 'endnote'); + $this->addComments($zip, $rId); $this->addChart($zip, $rId); // Write parts @@ -144,7 +143,7 @@ public function save($filename = null) } /** - * Get content types + * Get content types. * * @return array */ @@ -154,7 +153,7 @@ public function getContentTypes() } /** - * Get content types + * Get content types. * * @return array */ @@ -166,11 +165,9 @@ public function getRelationships() /** * Add header/footer media files, e.g. footer1.xml.rels. * - * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip * @param string $docPart - * @return void */ - private function addHeaderFooterMedia(ZipArchive $zip, $docPart) + private function addHeaderFooterMedia(ZipArchive $zip, $docPart): void { $elements = Media::getElements($docPart); if (!empty($elements)) { @@ -181,7 +178,7 @@ private function addHeaderFooterMedia(ZipArchive $zip, $docPart) $this->registerContentTypes($media); } - /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */ + /** @var Word2007\Part\AbstractPart $writerPart Type hint */ $writerPart = $this->getWriterPart('relspart')->setMedia($media); $zip->addFromString("word/_rels/{$file}.xml.rels", $writerPart->write()); } @@ -192,59 +189,53 @@ private function addHeaderFooterMedia(ZipArchive $zip, $docPart) /** * Add header/footer content. * - * @param \PhpOffice\PhpWord\Element\Section &$section - * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip * @param string $elmType header|footer - * @param integer &$rId - * @return void + * @param int &$rId */ - private function addHeaderFooterContent(Section &$section, ZipArchive $zip, $elmType, &$rId) + private function addHeaderFooterContent(Section &$section, ZipArchive $zip, $elmType, &$rId): void { $getFunction = $elmType == 'header' ? 'getHeaders' : 'getFooters'; $elmCount = ($section->getSectionId() - 1) * 3; $elements = $section->$getFunction(); + /** @var \PhpOffice\PhpWord\Element\AbstractElement $element Type hint */ foreach ($elements as &$element) { - /** @var \PhpOffice\PhpWord\Element\AbstractElement $element Type hint */ - $elmCount++; + ++$elmCount; $element->setRelationId(++$rId); $elmFile = "{$elmType}{$elmCount}.xml"; // e.g. footer1.xml $this->contentTypes['override']["/word/$elmFile"] = $elmType; - $this->relationships[] = array('target' => $elmFile, 'type' => $elmType, 'rID' => $rId); + $this->relationships[] = ['target' => $elmFile, 'type' => $elmType, 'rID' => $rId]; - /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */ + /** @var Word2007\Part\AbstractPart $writerPart Type hint */ $writerPart = $this->getWriterPart($elmType)->setElement($element); $zip->addFromString("word/$elmFile", $writerPart->write()); } } /** - * Add footnotes/endnotes + * Add footnotes/endnotes. * - * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip - * @param integer &$rId + * @param int &$rId * @param string $noteType - * @return void */ - private function addNotes(ZipArchive $zip, &$rId, $noteType = 'footnote') + private function addNotes(ZipArchive $zip, &$rId, $noteType = 'footnote'): void { $phpWord = $this->getPhpWord(); $noteType = ($noteType == 'endnote') ? 'endnote' : 'footnote'; $partName = "{$noteType}s"; - $method = 'get' . $partName; + $method = 'get' . ucfirst($partName); $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); $this->registerContentTypes($media); $this->contentTypes['override']["/word/{$partName}.xml"] = $partName; - $this->relationships[] = array('target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId); + $this->relationships[] = ['target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId]; // Write relationships file, e.g. word/_rels/footnotes.xml if (!empty($media)) { - /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */ + /** @var Word2007\Part\AbstractPart $writerPart Type hint */ $writerPart = $this->getWriterPart('relspart')->setMedia($media); $zip->addFromString("word/_rels/{$partName}.xml.rels", $writerPart->write()); } @@ -255,33 +246,52 @@ private function addNotes(ZipArchive $zip, &$rId, $noteType = 'footnote') } } + /** + * Add comments. + * + * @param int &$rId + */ + private function addComments(ZipArchive $zip, &$rId): void + { + $phpWord = $this->getPhpWord(); + $collection = $phpWord->getComments(); + $partName = 'comments'; + + // Add comment relations and contents + if ($collection->countItems() > 0) { + $this->relationships[] = ['target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId]; + + // Write content file, e.g. word/comments.xml + $writerPart = $this->getWriterPart($partName)->setElements($collection->getItems()); + $zip->addFromString("word/{$partName}.xml", $writerPart->write()); + } + } + /** * Add chart. * - * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip - * @param integer &$rId - * @return void + * @param int &$rId */ - private function addChart(ZipArchive $zip, &$rId) + private function addChart(ZipArchive $zip, &$rId): void { $phpWord = $this->getPhpWord(); $collection = $phpWord->getCharts(); $index = 0; if ($collection->countItems() > 0) { + /** @var \PhpOffice\PhpWord\Element\Chart $chart */ foreach ($collection->getItems() as $chart) { - $index++; - $rId++; + ++$index; + ++$rId; $filename = "charts/chart{$index}.xml"; // ContentTypes.xml $this->contentTypes['override']["/word/{$filename}"] = 'chart'; // word/_rels/document.xml.rel - $this->relationships[] = array('target' => $filename, 'type' => 'chart', 'rID' => $rId); + $this->relationships[] = ['target' => $filename, 'type' => 'chart', 'rID' => $rId]; // word/charts/chartN.xml - /** @var \PhpOffice\PhpWord\Element\Chart $chart */ $chart->setRelationId($rId); $writerPart = $this->getWriterPart('Chart'); $writerPart->setElement($chart); @@ -294,9 +304,8 @@ private function addChart(ZipArchive $zip, &$rId) * Register content types for each media. * * @param array $media - * @return void */ - private function registerContentTypes($media) + private function registerContentTypes($media): void { foreach ($media as $medium) { $mediumType = $medium['type']; diff --git a/src/PhpWord/Writer/Word2007/Element/AbstractElement.php b/src/PhpWord/Writer/Word2007/Element/AbstractElement.php index 3b6432ed6a..5743c8c7c7 100644 --- a/src/PhpWord/Writer/Word2007/Element/AbstractElement.php +++ b/src/PhpWord/Writer/Word2007/Element/AbstractElement.php @@ -1,4 +1,5 @@ xmlWriter = $xmlWriter; $this->element = $element; @@ -69,9 +73,9 @@ public function __construct(XMLWriter $xmlWriter, Element $element, $withoutP = } /** - * Get XML Writer + * Get XML Writer. * - * @return \PhpOffice\PhpWord\Shared\XMLWriter + * @return XMLWriter */ protected function getXmlWriter() { @@ -79,9 +83,9 @@ protected function getXmlWriter() } /** - * Get element + * Get element. * - * @return \PhpOffice\PhpWord\Element\AbstractElement + * @return Element */ protected function getElement() { @@ -92,9 +96,8 @@ protected function getElement() * Start w:p DOM element. * * @uses \PhpOffice\PhpWord\Writer\Word2007\Element\PageBreak::write() - * @return void */ - protected function startElementP() + protected function startElementP(): void { if (!$this->withoutP) { $this->xmlWriter->startElement('w:p'); @@ -103,70 +106,130 @@ protected function startElementP() $this->writeParagraphStyle(); } } + $this->writeCommentRangeStart(); } /** * End w:p DOM element. - * - * @return void */ - protected function endElementP() + protected function endElementP(): void { + $this->writeCommentRangeEnd(); if (!$this->withoutP) { $this->xmlWriter->endElement(); // w:p } } + /** + * Writes the w:commentRangeStart DOM element. + */ + protected function writeCommentRangeStart(): void + { + if ($this->element->getCommentsRangeStart() != null) { + foreach ($this->element->getCommentsRangeStart()->getItems() as $comment) { + $this->xmlWriter->writeElementBlock('w:commentRangeStart', ['w:id' => $comment->getElementId()]); + } + } + } + + /** + * Writes the w:commentRangeEnd DOM element. + */ + protected function writeCommentRangeEnd(): void + { + 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(); + } + } + } + } + /** * Write ending. - * - * @return void */ - protected function writeParagraphStyle() + protected function writeParagraphStyle(): void { $this->writeTextStyle('Paragraph'); } /** * Write ending. - * - * @return void */ - protected function writeFontStyle() + protected function writeFontStyle(): void { $this->writeTextStyle('Font'); } - /** * Write text style. * * @param string $styleType Font|Paragraph - * @return void */ - private function writeTextStyle($styleType) + private function writeTextStyle($styleType): void { $method = "get{$styleType}Style"; $class = "PhpOffice\\PhpWord\\Writer\\Word2007\\Style\\{$styleType}"; $styleObject = $this->element->$method(); + /** @var \PhpOffice\PhpWord\Writer\Word2007\Style\AbstractStyle $styleWriter Type Hint */ $styleWriter = new $class($this->xmlWriter, $styleObject); if (method_exists($styleWriter, 'setIsInline')) { $styleWriter->setIsInline(true); } - /** @var \PhpOffice\PhpWord\Writer\Word2007\Style\AbstractStyle $styleWriter */ $styleWriter->write(); } /** - * Convert text to valid format + * Convert text to valid format. * * @param string $text + * * @return string */ protected function getText($text) { - return String::controlCharacterPHP2OOXML($text); + return SharedText::controlCharacterPHP2OOXML($text); + } + + /** + * Write an XML text, this will call text() or writeRaw() depending on the value of Settings::isOutputEscapingEnabled(). + * + * @param string $content The text string to write + * + * @return bool Returns true on success or false on failure + */ + protected function writeText($content) + { + if (Settings::isOutputEscapingEnabled()) { + return $this->getXmlWriter()->text($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/Bookmark.php b/src/PhpWord/Writer/Word2007/Element/Bookmark.php index df5a104a18..ba61ad69fd 100644 --- a/src/PhpWord/Writer/Word2007/Element/Bookmark.php +++ b/src/PhpWord/Writer/Word2007/Element/Bookmark.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -41,7 +42,7 @@ public function write() $xmlWriter->writeAttribute('w:id', $rId); $xmlWriter->writeAttribute('w:name', $element->getName()); $xmlWriter->endElement(); - + $xmlWriter->startElement('w:bookmarkEnd'); $xmlWriter->writeAttribute('w:id', $rId); $xmlWriter->endElement(); diff --git a/src/PhpWord/Writer/Word2007/Element/Chart.php b/src/PhpWord/Writer/Word2007/Element/Chart.php index ccd8cd77db..6d4e18a9e4 100644 --- a/src/PhpWord/Writer/Word2007/Element/Chart.php +++ b/src/PhpWord/Writer/Word2007/Element/Chart.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -45,14 +44,15 @@ public function write() if (!$this->withoutP) { $xmlWriter->startElement('w:p'); } + $this->writeCommentRangeStart(); $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:drawing'); $xmlWriter->startElement('wp:inline'); // EMU - $xmlWriter->writeElementBlock('wp:extent', array('cx' => $style->getWidth(), 'cy' => $style->getHeight())); - $xmlWriter->writeElementBlock('wp:docPr', array('id' => $rId, 'name' => "Chart{$rId}")); + $xmlWriter->writeElementBlock('wp:extent', ['cx' => $style->getWidth(), 'cy' => $style->getHeight()]); + $xmlWriter->writeElementBlock('wp:docPr', ['id' => $rId, 'name' => "Chart{$rId}"]); $xmlWriter->startElement('a:graphic'); $xmlWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); diff --git a/src/PhpWord/Writer/Word2007/Element/CheckBox.php b/src/PhpWord/Writer/Word2007/Element/CheckBox.php index deafbd1de3..1adf7d6eb1 100644 --- a/src/PhpWord/Writer/Word2007/Element/CheckBox.php +++ b/src/PhpWord/Writer/Word2007/Element/CheckBox.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -63,18 +62,18 @@ public function write() $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:instrText'); $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw(' FORMCHECKBOX '); - $xmlWriter->endElement();// w:instrText + $xmlWriter->text(' FORMCHECKBOX '); + $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:fldChar $xmlWriter->endElement(); // w:r $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:fldChar'); $xmlWriter->writeAttribute('w:fldCharType', 'end'); - $xmlWriter->endElement();// w:fldChar + $xmlWriter->endElement(); // w:fldChar $xmlWriter->endElement(); // w:r $xmlWriter->startElement('w:r'); @@ -83,7 +82,7 @@ public function write() $xmlWriter->startElement('w:t'); $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw($this->getText($element->getText())); + $this->writeText($this->getText($element->getText())); $xmlWriter->endElement(); // w:t $xmlWriter->endElement(); // w:r diff --git a/src/PhpWord/Writer/Word2007/Element/Container.php b/src/PhpWord/Writer/Word2007/Element/Container.php index 771fe5c34e..85386f0b18 100644 --- a/src/PhpWord/Writer/Word2007/Element/Container.php +++ b/src/PhpWord/Writer/Word2007/Element/Container.php @@ -1,4 +1,5 @@ + */ + protected $containerWithoutP = ['TextRun', 'Footnote', 'Endnote', 'ListItemRun']; + /** * Write element. - * - * @return void */ - public function write() + public function write(): void { $container = $this->getElement(); if (!$container instanceof ContainerElement) { return; } $containerClass = substr(get_class($container), strrpos(get_class($container), '\\') + 1); - $withoutP = in_array($containerClass, array('TextRun', 'Footnote', 'Endnote', 'ListItemRun')) ? true : false; + $withoutP = in_array($containerClass, $this->containerWithoutP); $xmlWriter = $this->getXmlWriter(); // Loop through elements @@ -64,28 +68,24 @@ public function write() $writeLastTextBreak = ($containerClass == 'Cell') && ($elementClass == '' || $elementClass == 'Table'); if ($writeLastTextBreak) { $writerClass = $this->namespace . '\\TextBreak'; - /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $writer Type hint */ + /** @var AbstractElement $writer Type hint */ $writer = new $writerClass($xmlWriter, new TextBreakElement(), $withoutP); $writer->write(); } } /** - * Write individual element - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\AbstractElement $element - * @param bool $withoutP - * @return string + * Write individual element. */ - 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; if (class_exists($writerClass)) { - /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $writer Type hint */ + /** @var AbstractElement $writer Type hint */ $writer = new $writerClass($xmlWriter, $element, $withoutP); + $writer->setPart($this->getPart()); $writer->write(); } diff --git a/src/PhpWord/Writer/Word2007/Element/Endnote.php b/src/PhpWord/Writer/Word2007/Element/Endnote.php index f627c13c9d..6a00ed5b3b 100644 --- a/src/PhpWord/Writer/Word2007/Element/Endnote.php +++ b/src/PhpWord/Writer/Word2007/Element/Endnote.php @@ -1,4 +1,5 @@ getXmlWriter(); - $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\Field) { + $element = $this->getElement(); + if (!$element instanceof ElementField) { return; } + $methodName = 'write' . ucfirst(strtolower($element->getType())); + if (method_exists($this, $methodName)) { + $this->$methodName($element); + } else { + $this->writeDefault($element); + } + } + + private function writeDefault(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() . ' '; - $properties = $element->getProperties(); + if ($element->getText() != null) { + if (is_string($element->getText())) { + $instruction .= '"' . $element->getText() . '" '; + $instruction .= $this->buildPropertiesAndOptions($element); + } else { + $instruction .= '"'; + } + } else { + $instruction .= $this->buildPropertiesAndOptions($element); + } + $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 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 + } + + /** + * Writes a macrobutton field. + * + * //TODO A lot of code duplication with general method, should maybe be refactored + */ + protected function writeMacrobutton(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() . ' ' . $this->buildPropertiesAndOptions($element); + if (is_string($element->getText())) { + $instruction .= $element->getText() . ' '; + } + + $xmlWriter->startElement('w:r'); + $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 TextRun) { + $containerWriter = new Container($xmlWriter, $element->getText(), true); + $containerWriter->write(); + } + } + + $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 buildPropertiesAndOptions(ElementField $element) + { + $propertiesAndOptions = ''; + $properties = $element->getProperties(); foreach ($properties as $propkey => $propval) { switch ($propkey) { case 'format': + $propertiesAndOptions .= '\\* ' . $propval . ' '; + + break; case 'numformat': - $instruction .= '\* ' . $propval . ' '; + $propertiesAndOptions .= '\\# ' . $propval . ' '; + break; case 'dateformat': - $instruction .= '\@ "' . $propval . '" '; + $propertiesAndOptions .= '\\@ "' . $propval . '" '; + + break; + case 'macroname': + $propertiesAndOptions .= $propval . ' '; + + break; + default: + $propertiesAndOptions .= '"' . $propval . '" '; + break; } } @@ -55,36 +196,138 @@ public function write() foreach ($options as $option) { switch ($option) { case 'PreserveFormat': - $instruction .= '\* MERGEFORMAT '; + $propertiesAndOptions .= '\\* MERGEFORMAT '; + break; case 'LunarCalendar': - $instruction .= '\h '; + $propertiesAndOptions .= '\\h '; + break; case 'SakaEraCalendar': - $instruction .= '\s '; + $propertiesAndOptions .= '\\s '; + break; case 'LastUsedFormat': - $instruction .= '\l '; + $propertiesAndOptions .= '\\l '; + + break; + case 'Bold': + $propertiesAndOptions .= '\\b '; + + break; + case 'Italic': + $propertiesAndOptions .= '\\i '; + + break; + case 'Path': + $propertiesAndOptions .= '\\p '; + break; + default: + $propertiesAndOptions .= $option . ' '; } } + return $propertiesAndOptions; + } + + /** + * Writes a REF field. + */ + protected function writeRef(ElementField $element): void + { + $xmlWriter = $this->getXmlWriter(); $this->startElementP(); - $xmlWriter->startElement('w:fldSimple'); - $xmlWriter->writeAttribute('w:instr', $instruction); + $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 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:t'); - $xmlWriter->writeRaw('1'); - $xmlWriter->endElement(); // w:t + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'end'); + $xmlWriter->endElement(); // w:fldChar $xmlWriter->endElement(); // w:r - $xmlWriter->endElement(); // w:fldSimple $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 5640a90db4..68f998e390 100644 --- a/src/PhpWord/Writer/Word2007/Element/Footnote.php +++ b/src/PhpWord/Writer/Word2007/Element/Footnote.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -53,7 +52,7 @@ public function write() $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/FormField.php b/src/PhpWord/Writer/Word2007/Element/FormField.php index 432dc9c23b..1e58f58e55 100644 --- a/src/PhpWord/Writer/Word2007/Element/FormField.php +++ b/src/PhpWord/Writer/Word2007/Element/FormField.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -48,7 +48,7 @@ public function write() } $type = $element->getType(); - $instructions = array('textinput' => 'FORMTEXT', 'checkbox' => 'FORMCHECKBOX', 'dropdown' => 'FORMDROPDOWN'); + $instructions = ['textinput' => 'FORMTEXT', 'checkbox' => 'FORMCHECKBOX', 'dropdown' => 'FORMDROPDOWN']; $instruction = $instructions[$type]; $writeFormField = "write{$type}"; $name = $element->getName(); @@ -78,8 +78,8 @@ public function write() $this->writeFontStyle(); $xmlWriter->startElement('w:instrText'); $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw("{$instruction}"); - $xmlWriter->endElement();// w:instrText + $xmlWriter->text("{$instruction}"); + $xmlWriter->endElement(); // w:instrText $xmlWriter->endElement(); // w:r $xmlWriter->startElement('w:r'); @@ -91,7 +91,7 @@ public function write() $this->writeFontStyle(); $xmlWriter->startElement('w:t'); $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw($value); + $this->writeText($value); $xmlWriter->endElement(); // w:t $xmlWriter->endElement(); // w:r @@ -106,12 +106,9 @@ public function write() /** * Write textinput. * - * @link http://www.datypic.com/sc/ooxml/t-w_CT_FFTextInput.html - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\FormField $element - * @return void + * @see http://www.datypic.com/sc/ooxml/t-w_CT_FFTextInput.html */ - private function writeTextInput(XMLWriter $xmlWriter, FormFieldElement $element) + private function writeTextInput(XMLWriter $xmlWriter, FormFieldElement $element): void { $default = $element->getDefault(); @@ -123,12 +120,9 @@ private function writeTextInput(XMLWriter $xmlWriter, FormFieldElement $element) /** * Write checkbox. * - * @link http://www.datypic.com/sc/ooxml/t-w_CT_FFCheckBox.html - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\FormField $element - * @return void + * @see http://www.datypic.com/sc/ooxml/t-w_CT_FFCheckBox.html */ - private function writeCheckBox(XMLWriter $xmlWriter, FormFieldElement $element) + private function writeCheckBox(XMLWriter $xmlWriter, FormFieldElement $element): void { $default = $element->getDefault() ? 1 : 0; $value = $element->getValue(); @@ -147,12 +141,9 @@ private function writeCheckBox(XMLWriter $xmlWriter, FormFieldElement $element) /** * Write dropdown. * - * @link http://www.datypic.com/sc/ooxml/t-w_CT_FFDDList.html - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\FormField $element - * @return void + * @see http://www.datypic.com/sc/ooxml/t-w_CT_FFDDList.html */ - private function writeDropDown(XMLWriter $xmlWriter, FormFieldElement $element) + private function writeDropDown(XMLWriter $xmlWriter, FormFieldElement $element): void { $default = $element->getDefault(); $value = $element->getValue(); @@ -165,6 +156,9 @@ private function writeDropDown(XMLWriter $xmlWriter, FormFieldElement $element) $xmlWriter->writeElementBlock('w:result', 'w:val', $value); $xmlWriter->writeElementBlock('w:default', 'w:val', $default); foreach ($entries as $entry) { + if ($entry == null || $entry == '') { + $entry = str_repeat(' ', self::FILLER_LENGTH); + } $xmlWriter->writeElementBlock('w:listEntry', 'w:val', $entry); } $xmlWriter->endElement(); diff --git a/src/PhpWord/Writer/Word2007/Element/Formula.php b/src/PhpWord/Writer/Word2007/Element/Formula.php new file mode 100644 index 0000000000..9556706221 --- /dev/null +++ b/src/PhpWord/Writer/Word2007/Element/Formula.php @@ -0,0 +1,51 @@ +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/Image.php b/src/PhpWord/Writer/Word2007/Element/Image.php index 7398842397..7835f32ad5 100644 --- a/src/PhpWord/Writer/Word2007/Element/Image.php +++ b/src/PhpWord/Writer/Word2007/Element/Image.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -50,10 +52,8 @@ public function write() /** * Write image element. - * - * @return void */ - private function writeImage(XMLWriter $xmlWriter, ImageElement $element) + private function writeImage(XMLWriter $xmlWriter, ImageElement $element): void { $rId = $element->getRelationId() + ($element->isInSection() ? 6 : 0); $style = $element->getStyle(); @@ -63,11 +63,23 @@ private function writeImage(XMLWriter $xmlWriter, ImageElement $element) $xmlWriter->startElement('w:p'); $styleWriter->writeAlignment(); } + $this->writeCommentRangeStart(); $xmlWriter->startElement('w:r'); + + // Write position + $position = $style->getPosition(); + if ($position && $style->getWrap() == FrameStyle::WRAP_INLINE) { + $fontStyle = new FontStyle('text'); + $fontStyle->setPosition($position); + $fontStyleWriter = new FontStyleWriter($xmlWriter, $fontStyle); + $fontStyleWriter->write(); + } + $xmlWriter->startElement('w:pict'); $xmlWriter->startElement('v:shape'); $xmlWriter->writeAttribute('type', '#_x0000_t75'); + $xmlWriter->writeAttribute('stroked', 'f'); $styleWriter->write(); @@ -85,21 +97,22 @@ private function writeImage(XMLWriter $xmlWriter, ImageElement $element) /** * Write watermark element. - * - * @return void */ - private function writeWatermark(XMLWriter $xmlWriter, ImageElement $element) + private function writeWatermark(XMLWriter $xmlWriter, ImageElement $element): void { $rId = $element->getRelationId(); $style = $element->getStyle(); $style->setPositioning('absolute'); $styleWriter = new ImageStyleWriter($xmlWriter, $style); - $xmlWriter->startElement('w:p'); + if (!$this->withoutP) { + $xmlWriter->startElement('w:p'); + } $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:pict'); $xmlWriter->startElement('v:shape'); $xmlWriter->writeAttribute('type', '#_x0000_t75'); + $xmlWriter->writeAttribute('stroked', 'f'); $styleWriter->write(); @@ -110,6 +123,8 @@ private function writeWatermark(XMLWriter $xmlWriter, ImageElement $element) $xmlWriter->endElement(); // v:shape $xmlWriter->endElement(); // w:pict $xmlWriter->endElement(); // w:r - $xmlWriter->endElement(); // w:p + if (!$this->withoutP) { + $xmlWriter->endElement(); // w:p + } } } diff --git a/src/PhpWord/Writer/Word2007/Element/Line.php b/src/PhpWord/Writer/Word2007/Element/Line.php index a6c7c24081..fe8370653a 100644 --- a/src/PhpWord/Writer/Word2007/Element/Line.php +++ b/src/PhpWord/Writer/Word2007/Element/Line.php @@ -1,4 +1,5 @@ getXmlWriter(); - $element = $this->getElement(); + $element = $this->getElement(); if (!$element instanceof LineElement) { return; } @@ -48,6 +46,7 @@ public function write() $xmlWriter->startElement('w:p'); $styleWriter->writeAlignment(); } + $this->writeCommentRangeStart(); $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:pict'); diff --git a/src/PhpWord/Writer/Word2007/Element/Link.php b/src/PhpWord/Writer/Word2007/Element/Link.php index 2cb8407f5b..563343899d 100644 --- a/src/PhpWord/Writer/Word2007/Element/Link.php +++ b/src/PhpWord/Writer/Word2007/Element/Link.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -54,7 +53,7 @@ public function write() $xmlWriter->startElement('w:t'); $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw($element->getText()); + $this->writeText($element->getText()); $xmlWriter->endElement(); // w:t $xmlWriter->endElement(); // w:r $xmlWriter->endElement(); // w:hyperlink diff --git a/src/PhpWord/Writer/Word2007/Element/ListItem.php b/src/PhpWord/Writer/Word2007/Element/ListItem.php index 0f559a4e1d..a91301cdc0 100644 --- a/src/PhpWord/Writer/Word2007/Element/ListItem.php +++ b/src/PhpWord/Writer/Word2007/Element/ListItem.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); diff --git a/src/PhpWord/Writer/Word2007/Element/ListItemRun.php b/src/PhpWord/Writer/Word2007/Element/ListItemRun.php index 289cb05476..a20912a0a8 100644 --- a/src/PhpWord/Writer/Word2007/Element/ListItemRun.php +++ b/src/PhpWord/Writer/Word2007/Element/ListItemRun.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\ListItemRun) { + + if (!$element instanceof ListItemRunElement) { return; } + $this->writeParagraph($element); + } + + private function writeParagraph(ListItemRunElement $element): void + { + $xmlWriter = $this->getXmlWriter(); $xmlWriter->startElement('w:p'); + $this->writeParagraphProperties($element); + + $containerWriter = new Container($xmlWriter, $element); + $containerWriter->write(); + + $xmlWriter->endElement(); // w:p + } + + private function writeParagraphProperties(ListItemRunElement $element): void + { + $xmlWriter = $this->getXmlWriter(); $xmlWriter->startElement('w:pPr'); - $paragraphStyle = $element->getParagraphStyle(); - $styleWriter = new ParagraphStyleWriter($xmlWriter, $paragraphStyle); + + $styleWriter = new ParagraphStyleWriter($xmlWriter, $element->getParagraphStyle()); $styleWriter->setIsInline(true); + $styleWriter->setWithoutPPR(true); $styleWriter->write(); - $xmlWriter->startElement('w:numPr'); - $xmlWriter->startElement('w:ilvl'); - $xmlWriter->writeAttribute('w:val', $element->getDepth()); - $xmlWriter->endElement(); // w:ilvl - $xmlWriter->startElement('w:numId'); - $xmlWriter->writeAttribute('w:val', $element->getStyle()->getNumId()); - $xmlWriter->endElement(); // w:numId - $xmlWriter->endElement(); // w:numPr + $this->writeParagraphPropertiesNumbering($element); $xmlWriter->endElement(); // w:pPr + } - $containerWriter = new Container($xmlWriter, $element); - $containerWriter->write(); + private function writeParagraphPropertiesNumbering(ListItemRunElement $element): void + { + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:numPr'); - $xmlWriter->endElement(); // w:p + $xmlWriter->writeElementBlock('w:ilvl', [ + 'w:val' => $element->getDepth(), + ]); + + $xmlWriter->writeElementBlock('w:numId', [ + 'w:val' => $element->getStyle()->getNumId(), + ]); + + $xmlWriter->endElement(); // w:numPr } } diff --git a/src/PhpWord/Writer/Word2007/Element/Object.php b/src/PhpWord/Writer/Word2007/Element/OLEObject.php similarity index 90% rename from src/PhpWord/Writer/Word2007/Element/Object.php rename to src/PhpWord/Writer/Word2007/Element/OLEObject.php index a9cc449abf..f7db89defe 100644 --- a/src/PhpWord/Writer/Word2007/Element/Object.php +++ b/src/PhpWord/Writer/Word2007/Element/OLEObject.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\Object) { + if (!$element instanceof \PhpOffice\PhpWord\Element\OLEObject) { return; } @@ -51,6 +50,7 @@ public function write() $xmlWriter->startElement('w:p'); $styleWriter->writeAlignment(); } + $this->writeCommentRangeStart(); $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:object'); diff --git a/src/PhpWord/Writer/Word2007/Element/PageBreak.php b/src/PhpWord/Writer/Word2007/Element/PageBreak.php index fb831ca7ac..2e9ba4f202 100644 --- a/src/PhpWord/Writer/Word2007/Element/PageBreak.php +++ b/src/PhpWord/Writer/Word2007/Element/PageBreak.php @@ -1,4 +1,5 @@ getXmlWriter(); diff --git a/src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php b/src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php new file mode 100644 index 0000000000..2dbf229d63 --- /dev/null +++ b/src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php @@ -0,0 +1,61 @@ +attributes['w:val'] = $value; + } + + /** + * @since 0.13.0 + * + * @return string + */ + final public function getName() + { + return $this->name; + } + + /** + * @since 0.13.0 + * + * @return string[] + */ + final public function getAttributes() + { + return $this->attributes; + } +} diff --git a/src/PhpWord/Writer/Word2007/Element/PreserveText.php b/src/PhpWord/Writer/Word2007/Element/PreserveText.php index 894b3050f3..3ae708381f 100644 --- a/src/PhpWord/Writer/Word2007/Element/PreserveText.php +++ b/src/PhpWord/Writer/Word2007/Element/PreserveText.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -39,7 +38,7 @@ public function write() $texts = $element->getText(); if (!is_array($texts)) { - $texts = array($texts); + $texts = [$texts]; } $this->startElementP(); @@ -60,7 +59,7 @@ public function write() $xmlWriter->startElement('w:instrText'); $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw($text); + $this->writeText($text); $xmlWriter->endElement(); $xmlWriter->endElement(); @@ -82,7 +81,7 @@ public function write() $xmlWriter->startElement('w:t'); $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw($this->getText($text)); + $this->writeText($this->getText($text)); $xmlWriter->endElement(); $xmlWriter->endElement(); } diff --git a/src/PhpWord/Writer/Word2007/Element/Ruby.php b/src/PhpWord/Writer/Word2007/Element/Ruby.php new file mode 100644 index 0000000000..f30a5f7e84 --- /dev/null +++ b/src/PhpWord/Writer/Word2007/Element/Ruby.php @@ -0,0 +1,81 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Ruby) { + return; + } + /** @var \PhpOffice\PhpWord\Element\Ruby $element */ + $this->startElementP(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:ruby'); + + // write properties + $xmlWriter->startElement('w:rubyPr'); + $properties = $element->getProperties(); + $xmlWriter->startElement('w:rubyAlign'); + $xmlWriter->writeAttribute('w:val', $properties->getAlignment()); + $xmlWriter->endElement(); // w:rubyAlign + $xmlWriter->startElement('w:hps'); + $xmlWriter->writeAttribute('w:val', $properties->getFontFaceSize()); + $xmlWriter->endElement(); // w:hps + $xmlWriter->startElement('w:hpsRaise'); + $xmlWriter->writeAttribute('w:val', $properties->getFontPointsAboveBaseText()); + $xmlWriter->endElement(); // w:hpsRaise + $xmlWriter->startElement('w:hpsBaseText'); + $xmlWriter->writeAttribute('w:val', $properties->getFontSizeForBaseText()); + $xmlWriter->endElement(); // w:hpsBaseText + $xmlWriter->startElement('w:lid'); + $xmlWriter->writeAttribute('w:val', $properties->getLanguageId()); + $xmlWriter->endElement(); // w:lid + + $xmlWriter->endElement(); // w:rubyPr + + // write ruby text + $xmlWriter->startElement('w:rt'); + $rubyTextRun = $element->getRubyTextRun(); + $textRunWriter = new TextRun($xmlWriter, $rubyTextRun, true); + $textRunWriter->write(); + $xmlWriter->endElement(); // w:rt + // write base text + $xmlWriter->startElement('w:rubyBase'); + $baseTextRun = $element->getBaseTextRun(); + $textRunWriter = new TextRun($xmlWriter, $baseTextRun, true); + $textRunWriter->write(); + $xmlWriter->endElement(); // w:rubyBase + + $xmlWriter->endElement(); // w:ruby + $xmlWriter->endElement(); // w:r + + $this->endElementP(); + } +} diff --git a/src/PhpWord/Writer/Word2007/Element/SDT.php b/src/PhpWord/Writer/Word2007/Element/SDT.php index 79d7004dd6..dfe5ca9bca 100644 --- a/src/PhpWord/Writer/Word2007/Element/SDT.php +++ b/src/PhpWord/Writer/Word2007/Element/SDT.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -43,6 +43,12 @@ public function write() } $type = $element->getType(); $writeFormField = "write{$type}"; + $alias = $element->getAlias(); + $tag = $element->getTag(); + $value = $element->getValue(); + if ($value === null) { + $value = 'Pick value'; + } $this->startElementP(); @@ -50,17 +56,17 @@ public function write() // Properties $xmlWriter->startElement('w:sdtPr'); - $xmlWriter->writeElementBlock('w:id', 'w:val', rand(100000000, 999999999)); + $xmlWriter->writeElementIf($alias != null, 'w:alias', 'w:val', $alias); $xmlWriter->writeElementBlock('w:lock', 'w:val', 'sdtLocked'); + $xmlWriter->writeElementBlock('w:id', 'w:val', mt_rand(100000000, 999999999)); + $xmlWriter->writeElementIf($tag != null, 'w:tag', 'w:val', $tag); $this->$writeFormField($xmlWriter, $element); $xmlWriter->endElement(); // w:sdtPr // Content $xmlWriter->startElement('w:sdtContent'); $xmlWriter->startElement('w:r'); - $xmlWriter->startElement('w:t'); - $xmlWriter->writeRaw('Pick value'); - $xmlWriter->endElement(); // w:t + $xmlWriter->writeElement('w:t', $value); $xmlWriter->endElement(); // w:r $xmlWriter->endElement(); // w:sdtContent @@ -69,22 +75,30 @@ public function write() $this->endElementP(); // w:p } + /** + * Write text. + * + * @see http://www.datypic.com/sc/ooxml/t-w_CT_SdtText.html + */ + private function writePlainText(XMLWriter $xmlWriter): void + { + $xmlWriter->startElement('w:text'); + $xmlWriter->endElement(); // w:text + } + /** * Write combo box. * - * @link http://www.datypic.com/sc/ooxml/t-w_CT_SdtComboBox.html - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\SDT $element - * @return void + * @see http://www.datypic.com/sc/ooxml/t-w_CT_SdtComboBox.html */ - private function writeComboBox(XMLWriter $xmlWriter, SDTElement $element) + private function writeComboBox(XMLWriter $xmlWriter, SDTElement $element): void { $type = $element->getType(); $listItems = $element->getListItems(); $xmlWriter->startElement("w:{$type}"); foreach ($listItems as $key => $val) { - $xmlWriter->writeElementBlock('w:listItem', array('w:value' => $key, 'w:displayText' => $val)); + $xmlWriter->writeElementBlock('w:listItem', ['w:value' => $key, 'w:displayText' => $val]); } $xmlWriter->endElement(); // w:{$type} } @@ -92,25 +106,19 @@ private function writeComboBox(XMLWriter $xmlWriter, SDTElement $element) /** * Write drop down list. * - * @link http://www.datypic.com/sc/ooxml/t-w_CT_SdtDropDownList.html - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\SDT $element - * @return void + * @see http://www.datypic.com/sc/ooxml/t-w_CT_SdtDropDownList.html */ - private function writeDropDownList(XMLWriter $xmlWriter, SDTElement $element) + private function writeDropDownList(XMLWriter $xmlWriter, SDTElement $element): void { - $this->writecomboBox($xmlWriter, $element); + $this->writeComboBox($xmlWriter, $element); } /** * Write date. * - * @link http://www.datypic.com/sc/ooxml/t-w_CT_SdtDate.html - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\SDT $element - * @return void + * @see http://www.datypic.com/sc/ooxml/t-w_CT_SdtDate.html */ - private function writeDate(XMLWriter $xmlWriter, SDTElement $element) + private function writeDate(XMLWriter $xmlWriter, SDTElement $element): void { $type = $element->getType(); diff --git a/src/PhpWord/Writer/Word2007/Element/Shape.php b/src/PhpWord/Writer/Word2007/Element/Shape.php index bd9320a258..0af2831ba6 100644 --- a/src/PhpWord/Writer/Word2007/Element/Shape.php +++ b/src/PhpWord/Writer/Word2007/Element/Shape.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -55,6 +55,7 @@ public function write() if (!$this->withoutP) { $xmlWriter->startElement('w:p'); } + $this->writeCommentRangeStart(); $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:pict'); @@ -77,12 +78,8 @@ public function write() /** * Write arc. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Shape $style - * @return void */ - private function writeArc(XMLWriter $xmlWriter, ShapeStyle $style) + private function writeArc(XMLWriter $xmlWriter, ShapeStyle $style): void { $points = $this->getPoints('arc', $style->getPoints()); @@ -92,12 +89,8 @@ private function writeArc(XMLWriter $xmlWriter, ShapeStyle $style) /** * Write curve. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Shape $style - * @return void */ - private function writeCurve(XMLWriter $xmlWriter, ShapeStyle $style) + private function writeCurve(XMLWriter $xmlWriter, ShapeStyle $style): void { $points = $this->getPoints('curve', $style->getPoints()); @@ -108,12 +101,8 @@ private function writeCurve(XMLWriter $xmlWriter, ShapeStyle $style) /** * Write line. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Shape $style - * @return void */ - private function writeLine(XMLWriter $xmlWriter, ShapeStyle $style) + private function writeLine(XMLWriter $xmlWriter, ShapeStyle $style): void { $points = $this->getPoints('line', $style->getPoints()); @@ -123,50 +112,45 @@ private function writeLine(XMLWriter $xmlWriter, ShapeStyle $style) /** * Write polyline. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Shape $style - * @return void */ - private function writePolyline(XMLWriter $xmlWriter, ShapeStyle $style) + private function writePolyline(XMLWriter $xmlWriter, ShapeStyle $style): void { $xmlWriter->writeAttributeIf($style->getPoints() !== null, 'points', $style->getPoints()); } /** * Write rectangle. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Shape $style - * @return void */ - private function writeRoundRect(XMLWriter $xmlWriter, ShapeStyle $style) + private function writeRoundRect(XMLWriter $xmlWriter, ShapeStyle $style): void { $xmlWriter->writeAttribute('arcsize', $style->getRoundness()); } /** - * Set points + * Set points. * * @param string $type * @param string $value + * * @return array */ private function getPoints($type, $value) { - $points = array(); + $points = []; switch ($type) { case 'arc': case 'line': $points = explode(' ', $value); - @list($start, $end) = $points; - $points = array('start' => $start, 'end' => $end); + [$start, $end] = array_pad($points, 2, null); + $points = ['start' => $start, 'end' => $end]; + break; case 'curve': $points = explode(' ', $value); - @list($start, $end, $point1, $point2) = $points; - $points = array('start' => $start, 'end' => $end, 'point1' => $point1, 'point2' => $point2); + [$start, $end, $point1, $point2] = array_pad($points, 4, null); + $points = ['start' => $start, 'end' => $end, 'point1' => $point1, 'point2' => $point2]; + break; } diff --git a/src/PhpWord/Writer/Word2007/Element/TOC.php b/src/PhpWord/Writer/Word2007/Element/TOC.php index db2a65d07b..44c6ba11fa 100644 --- a/src/PhpWord/Writer/Word2007/Element/TOC.php +++ b/src/PhpWord/Writer/Word2007/Element/TOC.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -64,21 +64,15 @@ public function write() } /** - * Write title - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\TOC $element - * @param \PhpOffice\PhpWord\Element\Title $title - * @param bool $writeFieldMark - * @return void + * Write title. */ - private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, $title, $writeFieldMark) + 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'); @@ -100,8 +94,11 @@ private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, $title, $ $styleWriter->write(); } $xmlWriter->startElement('w:t'); - $xmlWriter->writeRaw($title->getText()); - $xmlWriter->endElement(); + + $titleText = $title->getText(); + $this->writeText(is_string($titleText) ? $titleText : ''); + + $xmlWriter->endElement(); // w:t $xmlWriter->endElement(); // w:r $xmlWriter->startElement('w:r'); @@ -117,10 +114,24 @@ private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, $title, $ $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:instrText'); $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw("PAGEREF _Toc{$rId} \h"); + $xmlWriter->text("PAGEREF $rId \\h"); $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'); @@ -133,14 +144,9 @@ private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, $title, $ } /** - * Write style - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\TOC $element - * @param int $indent - * @return void + * Write style. */ - private function writeStyle(XMLWriter $xmlWriter, TOCElement $element, $indent) + private function writeStyle(XMLWriter $xmlWriter, TOCElement $element, int $indent): void { $tocStyle = $element->getStyleTOC(); $fontStyle = $element->getStyleFont(); @@ -149,7 +155,7 @@ private function writeStyle(XMLWriter $xmlWriter, TOCElement $element, $indent) $xmlWriter->startElement('w:pPr'); // Paragraph - if ($isObject && !is_null($fontStyle->getParagraph())) { + if ($isObject && null !== $fontStyle->getParagraph()) { $styleWriter = new ParagraphStyleWriter($xmlWriter, $fontStyle->getParagraph()); $styleWriter->write(); } @@ -181,12 +187,8 @@ private function writeStyle(XMLWriter $xmlWriter, TOCElement $element, $indent) /** * Write TOC Field. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\TOC $element - * @return void */ - private function writeFieldMark(XMLWriter $xmlWriter, TOCElement $element) + private function writeFieldMark(XMLWriter $xmlWriter, TOCElement $element): void { $minDepth = $element->getMinDepth(); $maxDepth = $element->getMaxDepth(); @@ -200,7 +202,7 @@ private function writeFieldMark(XMLWriter $xmlWriter, TOCElement $element) $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:instrText'); $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw("TOC \o {$minDepth}-{$maxDepth} \h \z \u"); + $xmlWriter->text("TOC \\o {$minDepth}-{$maxDepth} \\h \\z \\u"); $xmlWriter->endElement(); $xmlWriter->endElement(); diff --git a/src/PhpWord/Writer/Word2007/Element/Table.php b/src/PhpWord/Writer/Word2007/Element/Table.php index f090d05c89..2bb1b3f3a8 100644 --- a/src/PhpWord/Writer/Word2007/Element/Table.php +++ b/src/PhpWord/Writer/Word2007/Element/Table.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -62,7 +61,7 @@ public function write() $styleWriter->write(); // Write rows - for ($i = 0; $i < $rowCount; $i++) { + for ($i = 0; $i < $rowCount; ++$i) { $this->writeRow($xmlWriter, $rows[$i]); } @@ -72,28 +71,10 @@ public function write() /** * Write column. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\Table $element - * @return void */ - private function writeColumns(XMLWriter $xmlWriter, TableElement $element) + private function writeColumns(XMLWriter $xmlWriter, TableElement $element): void { - $rows = $element->getRows(); - $rowCount = count($rows); - - $cellWidths = array(); - for ($i = 0; $i < $rowCount; $i++) { - $row = $rows[$i]; - $cells = $row->getCells(); - if (count($cells) <= count($cellWidths)) { - continue; - } - $cellWidths = array(); - foreach ($cells as $cell) { - $cellWidths[] = $cell->getWidth(); - } - } + $cellWidths = $element->findFirstDefinedCellWidths(); $xmlWriter->startElement('w:tblGrid'); foreach ($cellWidths as $width) { @@ -109,12 +90,8 @@ private function writeColumns(XMLWriter $xmlWriter, TableElement $element) /** * Write row. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\Row $row - * @return void */ - private function writeRow(XMLWriter $xmlWriter, RowElement $row) + private function writeRow(XMLWriter $xmlWriter, RowElement $row): void { $xmlWriter->startElement('w:tr'); @@ -127,8 +104,14 @@ private function writeRow(XMLWriter $xmlWriter, RowElement $row) } // 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 @@ -136,14 +119,9 @@ private function writeRow(XMLWriter $xmlWriter, RowElement $row) /** * Write cell. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\Cell $cell - * @return void */ - private function writeCell(XMLWriter $xmlWriter, CellElement $cell) + private function writeCell(XMLWriter $xmlWriter, CellElement $cell): void { - $xmlWriter->startElement('w:tc'); // Write style diff --git a/src/PhpWord/Writer/Word2007/Element/TableAlignment.php b/src/PhpWord/Writer/Word2007/Element/TableAlignment.php new file mode 100644 index 0000000000..9c0977b8fd --- /dev/null +++ b/src/PhpWord/Writer/Word2007/Element/TableAlignment.php @@ -0,0 +1,61 @@ +attributes['w:val'] = $value; + } + + /** + * @since 0.13.0 + * + * @return string + */ + final public function getName() + { + return $this->name; + } + + /** + * @since 0.13.0 + * + * @return string[] + */ + final public function getAttributes() + { + return $this->attributes; + } +} diff --git a/src/PhpWord/Writer/Word2007/Element/Text.php b/src/PhpWord/Writer/Word2007/Element/Text.php index cfb991c2d0..c11b2d517f 100644 --- a/src/PhpWord/Writer/Word2007/Element/Text.php +++ b/src/PhpWord/Writer/Word2007/Element/Text.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -39,16 +40,66 @@ public function write() $this->startElementP(); + $this->writeOpeningTrackChange(); + $xmlWriter->startElement('w:r'); $this->writeFontStyle(); - $xmlWriter->startElement('w:t'); + $textElement = 'w:t'; + //'w:delText' in case of deleted text + $changed = $element->getTrackChange(); + if ($changed != null && $changed->getChangeType() == TrackChange::DELETED) { + $textElement = 'w:delText'; + } + $xmlWriter->startElement($textElement); + $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw($this->getText($element->getText())); + $this->writeText($this->getText($element->getText())); $xmlWriter->endElement(); $xmlWriter->endElement(); // w:r + $this->writeClosingTrackChange(); + $this->endElementP(); // w:p } + + /** + * Write opening of changed element. + */ + protected function writeOpeningTrackChange(): void + { + $changed = $this->getElement()->getTrackChange(); + if ($changed == null) { + return; + } + + $xmlWriter = $this->getXmlWriter(); + + if (($changed->getChangeType() == TrackChange::INSERTED)) { + $xmlWriter->startElement('w:ins'); + } elseif ($changed->getChangeType() == TrackChange::DELETED) { + $xmlWriter->startElement('w:del'); + } + $xmlWriter->writeAttribute('w:author', $changed->getAuthor()); + if ($changed->getDate() != null) { + $xmlWriter->writeAttribute('w:date', $changed->getDate()->format('Y-m-d\TH:i:s\Z')); + } + $xmlWriter->writeAttribute('w:id', $this->getElement()->getElementId()); + } + + /** + * Write ending. + */ + protected function writeClosingTrackChange(): void + { + $changed = $this->getElement()->getTrackChange(); + if ($changed == null) { + return; + } + + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->endElement(); // w:ins|w:del + } } diff --git a/src/PhpWord/Writer/Word2007/Element/TextBox.php b/src/PhpWord/Writer/Word2007/Element/TextBox.php index fe62c644f3..79011d8d6f 100644 --- a/src/PhpWord/Writer/Word2007/Element/TextBox.php +++ b/src/PhpWord/Writer/Word2007/Element/TextBox.php @@ -1,17 +1,16 @@ getXmlWriter(); $element = $this->getElement(); @@ -45,12 +42,24 @@ public function write() $xmlWriter->startElement('w:p'); $styleWriter->writeAlignment(); } + $this->writeCommentRangeStart(); $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:pict'); $xmlWriter->startElement('v:shape'); $xmlWriter->writeAttribute('type', '#_x0000_t0202'); + if ($style->getBgColor()) { + $xmlWriter->writeAttribute('fillcolor', $style->getBgColor()); + } else { + $xmlWriter->writeAttribute('filled', 'f'); + } + + if (!$style->getBorderColor()) { + $xmlWriter->writeAttribute('stroked', 'f'); + $xmlWriter->writeAttribute('strokecolor', 'white'); + } + $styleWriter->write(); $styleWriter->writeBorder(); diff --git a/src/PhpWord/Writer/Word2007/Element/TextBreak.php b/src/PhpWord/Writer/Word2007/Element/TextBreak.php index fb52b86e62..4c2ecde7ed 100644 --- a/src/PhpWord/Writer/Word2007/Element/TextBreak.php +++ b/src/PhpWord/Writer/Word2007/Element/TextBreak.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); diff --git a/src/PhpWord/Writer/Word2007/Element/TextRun.php b/src/PhpWord/Writer/Word2007/Element/TextRun.php index 844e0b6bbb..865ceb742a 100644 --- a/src/PhpWord/Writer/Word2007/Element/TextRun.php +++ b/src/PhpWord/Writer/Word2007/Element/TextRun.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); diff --git a/src/PhpWord/Writer/Word2007/Element/Title.php b/src/PhpWord/Writer/Word2007/Element/Title.php index ce9aeea529..b69fbd40db 100644 --- a/src/PhpWord/Writer/Word2007/Element/Title.php +++ b/src/PhpWord/Writer/Word2007/Element/Title.php @@ -1,4 +1,5 @@ getXmlWriter(); $element = $this->getElement(); @@ -49,27 +48,38 @@ public function write() $xmlWriter->endElement(); } - $rId = $element->getRelationId(); - $bookmarkRId = $element->getPhpWord()->addBookmark(); + $bookmarkRId = null; + if ($element->getDepth() !== 0) { + $rId = $element->getRelationId(); + $bookmarkRId = $element->getPhpWord()->addBookmark(); - // Bookmark start for TOC - $xmlWriter->startElement('w:bookmarkStart'); - $xmlWriter->writeAttribute('w:id', $bookmarkRId); - $xmlWriter->writeAttribute('w:name', "_Toc{$rId}"); - $xmlWriter->endElement(); + // Bookmark start for TOC + $xmlWriter->startElement('w:bookmarkStart'); + $xmlWriter->writeAttribute('w:id', $bookmarkRId); + $xmlWriter->writeAttribute('w:name', "_Toc{$rId}"); + $xmlWriter->endElement(); //w:bookmarkStart + } // Actual text - $xmlWriter->startElement('w:r'); - $xmlWriter->startElement('w:t'); - $xmlWriter->writeRaw($this->getText($element->getText())); - $xmlWriter->endElement(); - $xmlWriter->endElement(); - - // Bookmark end - $xmlWriter->startElement('w:bookmarkEnd'); - $xmlWriter->writeAttribute('w:id', $bookmarkRId); - $xmlWriter->endElement(); + $text = $element->getText(); + if (is_string($text)) { + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:t'); + $this->writeText($text); + $xmlWriter->endElement(); // w:t + $xmlWriter->endElement(); // w:r + } + if ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { + $containerWriter = new Container($xmlWriter, $text); + $containerWriter->write(); + } - $xmlWriter->endElement(); + if ($element->getDepth() !== 0) { + // Bookmark end + $xmlWriter->startElement('w:bookmarkEnd'); + $xmlWriter->writeAttribute('w:id', $bookmarkRId); + $xmlWriter->endElement(); //w:bookmarkEnd + } + $xmlWriter->endElement(); //w:p } } diff --git a/src/PhpWord/Writer/Word2007/Part/AbstractPart.php b/src/PhpWord/Writer/Word2007/Part/AbstractPart.php index e26853d737..92ef1b521f 100644 --- a/src/PhpWord/Writer/Word2007/Part/AbstractPart.php +++ b/src/PhpWord/Writer/Word2007/Part/AbstractPart.php @@ -1,4 +1,5 @@ parentWriter = $writer; } /** - * Get parent writer + * Get parent writer. * - * @return \PhpOffice\PhpWord\Writer\AbstractWriter - * @throws \PhpOffice\PhpWord\Exception\Exception + * @return AbstractWriter */ public function getParentWriter() { - if (!is_null($this->parentWriter)) { + if (null !== $this->parentWriter) { return $this->parentWriter; - } else { - throw new Exception('No parent WriterInterface assigned.'); } + + throw new Exception('No parent WriterInterface assigned.'); } /** - * Get XML Writer + * Get XML Writer. * - * @return \PhpOffice\PhpWord\Shared\XMLWriter + * @return XMLWriter */ protected function getXmlWriter() { $useDiskCaching = false; - if (!is_null($this->parentWriter)) { + if (null !== $this->parentWriter) { if ($this->parentWriter->isUseDiskCaching()) { $useDiskCaching = true; } } if ($useDiskCaching) { - return new XMLWriter(XMLWriter::STORAGE_DISK, $this->parentWriter->getDiskCachingDirectory()); - } else { - return new XMLWriter(XMLWriter::STORAGE_MEMORY); + return new XMLWriter(XMLWriter::STORAGE_DISK, $this->parentWriter->getDiskCachingDirectory(), Settings::hasCompatibility()); + } + + return new XMLWriter(XMLWriter::STORAGE_MEMORY, './', Settings::hasCompatibility()); + } + + /** + * Write an XML text, this will call text() or writeRaw() depending on the value of Settings::isOutputEscapingEnabled(). + * + * @param string $content The text string to write + * + * @return bool Returns true on success or false on failure + */ + protected function writeText($content) + { + if (Settings::isOutputEscapingEnabled()) { + return $this->getXmlWriter()->text($content); } + + return $this->getXmlWriter()->writeRaw($content); } } diff --git a/src/PhpWord/Writer/Word2007/Part/Chart.php b/src/PhpWord/Writer/Word2007/Part/Chart.php index 8423762c30..65e686ebad 100644 --- a/src/PhpWord/Writer/Word2007/Part/Chart.php +++ b/src/PhpWord/Writer/Word2007/Part/Chart.php @@ -1,4 +1,5 @@ array('type' => 'pie', 'colors' => 1), - 'doughnut' => array('type' => 'doughnut', 'colors' => 1, 'hole' => 75, 'no3d' => true), - 'bar' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'bar'), - 'column' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'col'), - 'line' => array('type' => 'line', 'colors' => 0, 'axes' => true), - 'area' => array('type' => 'area', 'colors' => 0, 'axes' => true), - 'radar' => array('type' => 'radar', 'colors' => 0, 'axes' => true, 'radar' => 'standard', 'no3d' => true), - 'scatter' => array('type' => 'scatter', 'colors' => 0, 'axes' => true, 'scatter' => 'marker', 'no3d' => true), - ); + private $types = [ + 'pie' => ['type' => 'pie', 'colors' => 1], + 'doughnut' => ['type' => 'doughnut', 'colors' => 1, 'hole' => 75, 'no3d' => true], + 'bar' => ['type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'bar', 'grouping' => 'clustered'], + 'stacked_bar' => ['type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'bar', 'grouping' => 'stacked'], + 'percent_stacked_bar' => ['type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'bar', 'grouping' => 'percentStacked'], + 'column' => ['type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'col', 'grouping' => 'clustered'], + 'stacked_column' => ['type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'col', 'grouping' => 'stacked'], + 'percent_stacked_column' => ['type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'col', 'grouping' => 'percentStacked'], + 'line' => ['type' => 'line', 'colors' => 0, 'axes' => true], + 'area' => ['type' => 'area', 'colors' => 0, 'axes' => true], + 'radar' => ['type' => 'radar', 'colors' => 0, 'axes' => true, 'radar' => 'standard', 'no3d' => true], + 'scatter' => ['type' => 'scatter', 'colors' => 0, 'axes' => true, 'scatter' => 'marker', 'no3d' => true], + ]; /** - * Chart options + * Chart options. * * @var array */ - private $options = array(); + private $options = []; /** * Set chart element. - * - * @param \PhpOffice\PhpWord\Element\Chart $element - * @return void */ - public function setElement(ChartElement $element) + public function setElement(ChartElement $element): void { $this->element = $element; } /** - * Write part + * Write part. * * @return string */ @@ -93,18 +95,14 @@ public function write() } /** - * Write chart + * Write chart. * - * @link http://www.datypic.com/sc/ooxml/t-draw-chart_CT_Chart.html - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @return void + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_Chart.html */ - private function writeChart(XMLWriter $xmlWriter) + private function writeChart(XMLWriter $xmlWriter): void { $xmlWriter->startElement('c:chart'); - $xmlWriter->writeElementBlock('c:autoTitleDeleted', 'val', 1); - $this->writePlotArea($xmlWriter); $xmlWriter->endElement(); // c:chart @@ -113,29 +111,56 @@ private function writeChart(XMLWriter $xmlWriter) /** * Write plot area. * - * @link http://www.datypic.com/sc/ooxml/t-draw-chart_CT_PlotArea.html - * @link http://www.datypic.com/sc/ooxml/t-draw-chart_CT_PieChart.html - * @link http://www.datypic.com/sc/ooxml/t-draw-chart_CT_DoughnutChart.html - * @link http://www.datypic.com/sc/ooxml/t-draw-chart_CT_BarChart.html - * @link http://www.datypic.com/sc/ooxml/t-draw-chart_CT_LineChart.html - * @link http://www.datypic.com/sc/ooxml/t-draw-chart_CT_AreaChart.html - * @link http://www.datypic.com/sc/ooxml/t-draw-chart_CT_RadarChart.html - * @link http://www.datypic.com/sc/ooxml/t-draw-chart_CT_ScatterChart.html - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @return void + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_PlotArea.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_PieChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_DoughnutChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_BarChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_LineChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_AreaChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_RadarChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_ScatterChart.html */ - private function writePlotArea(XMLWriter $xmlWriter) + private function writePlotArea(XMLWriter $xmlWriter): void { $type = $this->element->getType(); $style = $this->element->getStyle(); $this->options = $this->types[$type]; + $title = $style->getTitle(); + $showLegend = $style->isShowLegend(); + $legendPosition = $style->getLegendPosition(); + + //Chart title + if ($title) { + $xmlWriter->startElement('c:title'); + $xmlWriter->startElement('c:tx'); + $xmlWriter->startElement('c:rich'); + $xmlWriter->writeRaw(' + + + + + ' . $title . ' + + '); + $xmlWriter->endElement(); // c:rich + $xmlWriter->endElement(); // c:tx + $xmlWriter->endElement(); // c:title + } else { + $xmlWriter->writeElementBlock('c:autoTitleDeleted', 'val', 1); + } + + //Chart legend + if ($showLegend) { + $xmlWriter->writeRaw(''); + } + $xmlWriter->startElement('c:plotArea'); $xmlWriter->writeElement('c:layout'); // Chart $chartType = $this->options['type']; - $chartType .= $style->is3d() && !isset($this->options['no3d'])? '3D' : ''; + $chartType .= $style->is3d() && !isset($this->options['no3d']) ? '3D' : ''; $chartType .= 'Chart'; $xmlWriter->startElement("c:{$chartType}"); @@ -148,7 +173,7 @@ private function writePlotArea(XMLWriter $xmlWriter) } if (isset($this->options['bar'])) { $xmlWriter->writeElementBlock('c:barDir', 'val', $this->options['bar']); // bar|col - $xmlWriter->writeElementBlock('c:grouping', 'val', 'clustered'); // 3d; standard = percentStacked + $xmlWriter->writeElementBlock('c:grouping', 'val', $this->options['grouping']); // 3d; standard = percentStacked } if (isset($this->options['radar'])) { $xmlWriter->writeElementBlock('c:radarStyle', 'val', $this->options['radar']); @@ -160,6 +185,11 @@ private function writePlotArea(XMLWriter $xmlWriter) // Series $this->writeSeries($xmlWriter, isset($this->options['scatter'])); + // don't overlap if grouping is 'clustered' + if (!isset($this->options['grouping']) || $this->options['grouping'] != 'clustered') { + $xmlWriter->writeElementBlock('c:overlap', 'val', '100'); + } + // Axes if (isset($this->options['axes'])) { $xmlWriter->writeElementBlock('c:axId', 'val', 1); @@ -180,15 +210,16 @@ private function writePlotArea(XMLWriter $xmlWriter) /** * Write series. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param bool $scatter - * @return void */ - private function writeSeries(XMLWriter $xmlWriter, $scatter = false) + private function writeSeries(XMLWriter $xmlWriter, $scatter = false): void { $series = $this->element->getSeries(); + $style = $this->element->getStyle(); + $colors = $style->getColors(); $index = 0; + $colorIndex = 0; foreach ($series as $seriesItem) { $categories = $seriesItem['categories']; $values = $seriesItem['values']; @@ -198,6 +229,32 @@ private function writeSeries(XMLWriter $xmlWriter, $scatter = false) $xmlWriter->writeElementBlock('c:idx', 'val', $index); $xmlWriter->writeElementBlock('c:order', 'val', $index); + if (null !== $seriesItem['name'] && $seriesItem['name'] != '') { + $xmlWriter->startElement('c:tx'); + $xmlWriter->startElement('c:strRef'); + $xmlWriter->startElement('c:strCache'); + $xmlWriter->writeElementBlock('c:ptCount', 'val', 1); + $xmlWriter->startElement('c:pt'); + $xmlWriter->writeAttribute('idx', 0); + $xmlWriter->startElement('c:v'); + $xmlWriter->writeRaw($seriesItem['name']); + $xmlWriter->endElement(); // c:v + $xmlWriter->endElement(); // c:pt + $xmlWriter->endElement(); // c:strCache + $xmlWriter->endElement(); // c:strRef + $xmlWriter->endElement(); // c:tx + } + + // The c:dLbls was added to make word charts look more like the reports in SurveyGizmo + // This section needs to be made configurable before a pull request is made + $xmlWriter->startElement('c:dLbls'); + + foreach ($style->getDataLabelOptions() as $option => $val) { + $xmlWriter->writeElementBlock("c:{$option}", 'val', (int) $val); + } + + $xmlWriter->endElement(); // c:dLbls + if (isset($this->options['scatter'])) { $this->writeShape($xmlWriter); } @@ -208,46 +265,64 @@ private function writeSeries(XMLWriter $xmlWriter, $scatter = false) } else { $this->writeSeriesItem($xmlWriter, 'cat', $categories); $this->writeSeriesItem($xmlWriter, 'val', $values); + + // check that there are colors + if (is_array($colors) && count($colors) > 0) { + // assign a color to each value + $valueIndex = 0; + for ($i = 0; $i < count($values); ++$i) { + // check that there are still enought colors + $xmlWriter->startElement('c:dPt'); + $xmlWriter->writeElementBlock('c:idx', 'val', $valueIndex); + $xmlWriter->startElement('c:spPr'); + $xmlWriter->startElement('a:solidFill'); + $xmlWriter->writeElementBlock('a:srgbClr', 'val', $colors[$colorIndex++ % count($colors)]); + $xmlWriter->endElement(); // a:solidFill + $xmlWriter->endElement(); // c:spPr + $xmlWriter->endElement(); // c:dPt + ++$valueIndex; + } + } } $xmlWriter->endElement(); // c:ser - $index++; + ++$index; } - } /** * Write series items. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param string $type * @param array $values - * @return void */ - private function writeSeriesItem(XMLWriter $xmlWriter, $type, $values) + private function writeSeriesItem(XMLWriter $xmlWriter, $type, $values): void { - $types = array( - 'cat' => array('c:cat', 'c:strLit'), - 'val' => array('c:val', 'c:numLit'), - 'xVal' => array('c:xVal', 'c:strLit'), - 'yVal' => array('c:yVal', 'c:numLit'), - ); - list($itemType, $itemLit) = $types[$type]; + $types = [ + 'cat' => ['c:cat', 'c:strLit'], + 'val' => ['c:val', 'c:numLit'], + 'xVal' => ['c:xVal', 'c:strLit'], + 'yVal' => ['c:yVal', 'c:numLit'], + ]; + [$itemType, $itemLit] = $types[$type]; $xmlWriter->startElement($itemType); $xmlWriter->startElement($itemLit); + $xmlWriter->writeElementBlock('c:ptCount', 'val', count($values)); $index = 0; foreach ($values as $value) { $xmlWriter->startElement('c:pt'); $xmlWriter->writeAttribute('idx', $index); - - $xmlWriter->startElement('c:v'); - $xmlWriter->writeRaw($value); - $xmlWriter->endElement(); // c:v - + if (\PhpOffice\PhpWord\Settings::isOutputEscapingEnabled()) { + $xmlWriter->writeElement('c:v', $value); + } else { + $xmlWriter->startElement('c:v'); + $xmlWriter->writeRaw($value); + $xmlWriter->endElement(); // c:v + } $xmlWriter->endElement(); // c:pt - $index++; + ++$index; } $xmlWriter->endElement(); // $itemLit @@ -255,36 +330,58 @@ private function writeSeriesItem(XMLWriter $xmlWriter, $type, $values) } /** - * Write axis + * Write axis. + * + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_CatAx.html * - * @link http://www.datypic.com/sc/ooxml/t-draw-chart_CT_CatAx.html - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param string $type - * @return void */ - private function writeAxis(XMLWriter $xmlWriter, $type) + private function writeAxis(XMLWriter $xmlWriter, $type): void { - $types = array( - 'cat' => array('c:catAx', 1, 'b', 2), - 'val' => array('c:valAx', 2, 'l', 1), - ); - list($axisType, $axisId, $axisPos, $axisCross) = $types[$type]; + $style = $this->element->getStyle(); + $types = [ + 'cat' => ['c:catAx', 1, 'b', 2], + 'val' => ['c:valAx', 2, 'l', 1], + ]; + [$axisType, $axisId, $axisPos, $axisCross] = $types[$type]; $xmlWriter->startElement($axisType); $xmlWriter->writeElementBlock('c:axId', 'val', $axisId); $xmlWriter->writeElementBlock('c:axPos', 'val', $axisPos); + + $categoryAxisTitle = $style->getCategoryAxisTitle(); + $valueAxisTitle = $style->getValueAxisTitle(); + + if ($axisType == 'c:catAx') { + if (null !== $categoryAxisTitle) { + $this->writeAxisTitle($xmlWriter, $categoryAxisTitle); + } + } elseif ($axisType == 'c:valAx') { + if (null !== $valueAxisTitle) { + $this->writeAxisTitle($xmlWriter, $valueAxisTitle); + } + } + $xmlWriter->writeElementBlock('c:crossAx', 'val', $axisCross); $xmlWriter->writeElementBlock('c:auto', 'val', 1); if (isset($this->options['axes'])) { $xmlWriter->writeElementBlock('c:delete', 'val', 0); - $xmlWriter->writeElementBlock('c:majorTickMark', 'val', 'none'); + $xmlWriter->writeElementBlock('c:majorTickMark', 'val', $style->getMajorTickPosition()); $xmlWriter->writeElementBlock('c:minorTickMark', 'val', 'none'); - $xmlWriter->writeElementBlock('c:tickLblPos', 'val', 'none'); // nextTo + if ($style->showAxisLabels()) { + if ($axisType == 'c:catAx') { + $xmlWriter->writeElementBlock('c:tickLblPos', 'val', $style->getCategoryLabelPosition()); + } else { + $xmlWriter->writeElementBlock('c:tickLblPos', 'val', $style->getValueLabelPosition()); + } + } else { + $xmlWriter->writeElementBlock('c:tickLblPos', 'val', 'none'); + } $xmlWriter->writeElementBlock('c:crosses', 'val', 'autoZero'); } - if (isset($this->options['radar'])) { + if (isset($this->options['radar']) || ($type == 'cat' && $style->showGridX()) || ($type == 'val' && $style->showGridY())) { $xmlWriter->writeElement('c:majorGridlines'); } @@ -298,14 +395,13 @@ private function writeAxis(XMLWriter $xmlWriter, $type) } /** - * Write shape + * Write shape. + * + * @see http://www.datypic.com/sc/ooxml/t-a_CT_ShapeProperties.html * - * @link http://www.datypic.com/sc/ooxml/t-a_CT_ShapeProperties.html - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param bool $line - * @return void */ - private function writeShape(XMLWriter $xmlWriter, $line = false) + private function writeShape(XMLWriter $xmlWriter, $line = false): void { $xmlWriter->startElement('c:spPr'); $xmlWriter->startElement('a:ln'); @@ -317,4 +413,30 @@ private function writeShape(XMLWriter $xmlWriter, $line = false) $xmlWriter->endElement(); // a:ln $xmlWriter->endElement(); // c:spPr } + + private function writeAxisTitle(XMLWriter $xmlWriter, $title): void + { + $xmlWriter->startElement('c:title'); //start c:title + $xmlWriter->startElement('c:tx'); //start c:tx + $xmlWriter->startElement('c:rich'); // start c:rich + $xmlWriter->writeElement('a:bodyPr'); + $xmlWriter->writeElement('a:lstStyle'); + $xmlWriter->startElement('a:p'); + $xmlWriter->startElement('a:pPr'); + $xmlWriter->writeElement('a:defRPr'); + $xmlWriter->endElement(); // end a:pPr + $xmlWriter->startElement('a:r'); + $xmlWriter->writeElementBlock('a:rPr', 'lang', 'en-US'); + + $xmlWriter->startElement('a:t'); + $xmlWriter->writeRaw($title); + $xmlWriter->endElement(); //end a:t + + $xmlWriter->endElement(); // end a:r + $xmlWriter->endElement(); //end a:p + $xmlWriter->endElement(); //end c:rich + $xmlWriter->endElement(); // end c:tx + $xmlWriter->writeElementBlock('c:overlay', 'val', '0'); + $xmlWriter->endElement(); // end c:title + } } diff --git a/src/PhpWord/Writer/Word2007/Part/Comments.php b/src/PhpWord/Writer/Word2007/Part/Comments.php new file mode 100644 index 0000000000..e1d7d05340 --- /dev/null +++ b/src/PhpWord/Writer/Word2007/Part/Comments.php @@ -0,0 +1,103 @@ +getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('w:comments'); + $xmlWriter->writeAttribute('xmlns:ve', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $xmlWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->writeAttribute('xmlns:m', 'http://schemas.openxmlformats.org/officeDocument/2006/math'); + $xmlWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); + $xmlWriter->writeAttribute('xmlns:wp', 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'); + $xmlWriter->writeAttribute('xmlns:w10', 'urn:schemas-microsoft-com:office:word'); + $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); + $xmlWriter->writeAttribute('xmlns:wne', 'http://schemas.microsoft.com/office/word/2006/wordml'); + + if ($this->elements !== null) { + foreach ($this->elements as $element) { + if ($element instanceof Comment) { + $this->writeComment($xmlWriter, $element); + } + } + } + + $xmlWriter->endElement(); // w:comments + + return $xmlWriter->getData(); + } + + /** + * Write comment item. + */ + protected function writeComment(XMLWriter $xmlWriter, Comment $comment): void + { + $xmlWriter->startElement('w:comment'); + $xmlWriter->writeAttribute('w:id', $comment->getElementId()); + $xmlWriter->writeAttribute('w:author', $comment->getAuthor()); + if ($comment->getDate() != null) { + $xmlWriter->writeAttribute('w:date', $comment->getDate()->format($this->dateFormat)); + } + $xmlWriter->writeAttributeIf($comment->getInitials() != null, 'w:initials', $comment->getInitials()); + + $containerWriter = new Container($xmlWriter, $comment); + $containerWriter->write(); + + $xmlWriter->endElement(); // w:comment + } + + /** + * Set element. + * + * @param Comment[] $elements + * + * @return self + */ + public function setElements($elements) + { + $this->elements = $elements; + + return $this; + } +} diff --git a/src/PhpWord/Writer/Word2007/Part/ContentTypes.php b/src/PhpWord/Writer/Word2007/Part/ContentTypes.php index 6ae4e8758e..2973eea15b 100644 --- a/src/PhpWord/Writer/Word2007/Part/ContentTypes.php +++ b/src/PhpWord/Writer/Word2007/Part/ContentTypes.php @@ -1,4 +1,5 @@ getContentTypes(); $openXMLPrefix = 'application/vnd.openxmlformats-'; - $wordMLPrefix = $openXMLPrefix . 'officedocument.wordprocessingml.'; - $drawingMLPrefix = $openXMLPrefix . 'officedocument.drawingml.'; - $overrides = array( - '/docProps/core.xml' => $openXMLPrefix . 'package.core-properties+xml', - '/docProps/app.xml' => $openXMLPrefix . 'officedocument.extended-properties+xml', - '/docProps/custom.xml' => $openXMLPrefix . 'officedocument.custom-properties+xml', - '/word/document.xml' => $wordMLPrefix . 'document.main+xml', - '/word/styles.xml' => $wordMLPrefix . 'styles+xml', - '/word/numbering.xml' => $wordMLPrefix . 'numbering+xml', - '/word/settings.xml' => $wordMLPrefix . 'settings+xml', + $wordMLPrefix = $openXMLPrefix . 'officedocument.wordprocessingml.'; + $drawingMLPrefix = $openXMLPrefix . 'officedocument.drawingml.'; + $overrides = [ + '/docProps/core.xml' => $openXMLPrefix . 'package.core-properties+xml', + '/docProps/app.xml' => $openXMLPrefix . 'officedocument.extended-properties+xml', + '/docProps/custom.xml' => $openXMLPrefix . 'officedocument.custom-properties+xml', + '/word/document.xml' => $wordMLPrefix . 'document.main+xml', + '/word/styles.xml' => $wordMLPrefix . 'styles+xml', + '/word/numbering.xml' => $wordMLPrefix . 'numbering+xml', + '/word/settings.xml' => $wordMLPrefix . 'settings+xml', '/word/theme/theme1.xml' => $openXMLPrefix . 'officedocument.theme+xml', - '/word/webSettings.xml' => $wordMLPrefix . 'webSettings+xml', - '/word/fontTable.xml' => $wordMLPrefix . 'fontTable+xml', - ); + '/word/webSettings.xml' => $wordMLPrefix . 'webSettings+xml', + '/word/fontTable.xml' => $wordMLPrefix . 'fontTable+xml', + '/word/comments.xml' => $wordMLPrefix . 'comments+xml', + ]; $defaults = $contentTypes['default']; if (!empty($contentTypes['override'])) { @@ -77,14 +79,13 @@ public function write() } /** - * Write content types element + * Write content types element. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter XML Writer + * @param XMLWriter $xmlWriter XML Writer * @param array $parts - * @param boolean $isDefault - * @return void + * @param bool $isDefault */ - private function writeContentType(XMLWriter $xmlWriter, $parts, $isDefault) + private function writeContentType(XMLWriter $xmlWriter, $parts, $isDefault): void { foreach ($parts as $partName => $contentType) { $partType = $isDefault ? 'Default' : 'Override'; diff --git a/src/PhpWord/Writer/Word2007/Part/DocPropsApp.php b/src/PhpWord/Writer/Word2007/Part/DocPropsApp.php index 421ceefec2..aaba2fcf37 100644 --- a/src/PhpWord/Writer/Word2007/Part/DocPropsApp.php +++ b/src/PhpWord/Writer/Word2007/Part/DocPropsApp.php @@ -1,4 +1,5 @@ startElement('dcterms:created'); $xmlWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); - $xmlWriter->writeRaw(date($this->dateFormat, $phpWord->getDocInfo()->getCreated())); + $xmlWriter->text(date($this->dateFormat, $phpWord->getDocInfo()->getCreated())); $xmlWriter->endElement(); // dcterms:modified $xmlWriter->startElement('dcterms:modified'); $xmlWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); - $xmlWriter->writeRaw(date($this->dateFormat, $phpWord->getDocInfo()->getModified())); + $xmlWriter->text(date($this->dateFormat, $phpWord->getDocInfo()->getModified())); $xmlWriter->endElement(); $xmlWriter->endElement(); // cp:coreProperties diff --git a/src/PhpWord/Writer/Word2007/Part/DocPropsCustom.php b/src/PhpWord/Writer/Word2007/Part/DocPropsCustom.php index ba6547d9b9..53297787d6 100644 --- a/src/PhpWord/Writer/Word2007/Part/DocPropsCustom.php +++ b/src/PhpWord/Writer/Word2007/Part/DocPropsCustom.php @@ -1,4 +1,5 @@ writeElement('vt:i4', $propertyValue); + break; case 'f': $xmlWriter->writeElement('vt:r8', $propertyValue); + break; case 'b': $xmlWriter->writeElement('vt:bool', ($propertyValue) ? 'true' : 'false'); + break; case 'd': - $xmlWriter->startElement('vt:filetime'); - $xmlWriter->writeRaw(date($this->dateFormat, $propertyValue)); - $xmlWriter->endElement(); + if ($propertyValue instanceof DateTime) { + $xmlWriter->writeElement('vt:filetime', $propertyValue->format($this->dateFormat)); + } else { + $xmlWriter->writeElement('vt:filetime', date($this->dateFormat, $propertyValue)); + } + break; default: $xmlWriter->writeElement('vt:lpwstr', $propertyValue); + break; } $xmlWriter->endElement(); // property diff --git a/src/PhpWord/Writer/Word2007/Part/Document.php b/src/PhpWord/Writer/Word2007/Part/Document.php index ea607f09b7..9a2ec09ef2 100644 --- a/src/PhpWord/Writer/Word2007/Part/Document.php +++ b/src/PhpWord/Writer/Word2007/Part/Document.php @@ -1,4 +1,5 @@ startElement('w:body'); - if ($sectionCount > 0) { foreach ($sections as $section) { - $currentSection++; + ++$currentSection; $containerWriter = new Container($xmlWriter, $section); $containerWriter->write(); @@ -80,12 +80,8 @@ public function write() /** * Write begin section. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\Section $section - * @return void */ - private function writeSection(XMLWriter $xmlWriter, Section $section) + private function writeSection(XMLWriter $xmlWriter, Section $section): void { $xmlWriter->startElement('w:p'); $xmlWriter->startElement('w:pPr'); @@ -96,12 +92,8 @@ private function writeSection(XMLWriter $xmlWriter, Section $section) /** * Write end section. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Element\Section $section - * @return void */ - private function writeSectionSettings(XMLWriter $xmlWriter, Section $section) + private function writeSectionSettings(XMLWriter $xmlWriter, Section $section): void { $xmlWriter->startElement('w:sectPr'); @@ -129,6 +121,32 @@ private function writeSectionSettings(XMLWriter $xmlWriter, Section $section) $xmlWriter->endElement(); } + // Footnote properties + if ($section->getFootnoteProperties() !== null) { + $xmlWriter->startElement('w:footnotePr'); + if ($section->getFootnoteProperties()->getPos() != null) { + $xmlWriter->startElement('w:pos'); + $xmlWriter->writeAttribute('w:val', $section->getFootnoteProperties()->getPos()); + $xmlWriter->endElement(); + } + if ($section->getFootnoteProperties()->getNumFmt() != null) { + $xmlWriter->startElement('w:numFmt'); + $xmlWriter->writeAttribute('w:val', $section->getFootnoteProperties()->getNumFmt()); + $xmlWriter->endElement(); + } + if ($section->getFootnoteProperties()->getNumStart() != null) { + $xmlWriter->startElement('w:numStart'); + $xmlWriter->writeAttribute('w:val', $section->getFootnoteProperties()->getNumStart()); + $xmlWriter->endElement(); + } + if ($section->getFootnoteProperties()->getNumRestart() != null) { + $xmlWriter->startElement('w:numRestart'); + $xmlWriter->writeAttribute('w:val', $section->getFootnoteProperties()->getNumRestart()); + $xmlWriter->endElement(); + } + $xmlWriter->endElement(); + } + // Section settings $styleWriter = new SectionStyleWriter($xmlWriter, $section->getStyle()); $styleWriter->write(); diff --git a/src/PhpWord/Writer/Word2007/Part/Endnotes.php b/src/PhpWord/Writer/Word2007/Part/Endnotes.php index f07bac5f70..5e733834db 100644 --- a/src/PhpWord/Writer/Word2007/Part/Endnotes.php +++ b/src/PhpWord/Writer/Word2007/Part/Endnotes.php @@ -1,4 +1,5 @@ '; $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; return $str; diff --git a/src/PhpWord/Writer/Word2007/Part/Footer.php b/src/PhpWord/Writer/Word2007/Part/Footer.php index db6a2b334b..0e2f55e74e 100644 --- a/src/PhpWord/Writer/Word2007/Part/Footer.php +++ b/src/PhpWord/Writer/Word2007/Part/Footer.php @@ -1,4 +1,5 @@ startElement($this->elementNode); - $xmlWriter->writeAttribute('w:id', $element->getRelationId()); + $xmlWriter->writeAttribute('w:id', $element->getRelationId() + 1); $xmlWriter->startElement('w:p'); // Paragraph style @@ -164,7 +164,7 @@ protected function writeNote(XMLWriter $xmlWriter, $element) $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:t'); $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw(' '); + $xmlWriter->text(' '); $xmlWriter->endElement(); // w:t $xmlWriter->endElement(); // w:r diff --git a/src/PhpWord/Writer/Word2007/Part/Header.php b/src/PhpWord/Writer/Word2007/Part/Header.php index 638111d7f3..1fafc79129 100644 --- a/src/PhpWord/Writer/Word2007/Part/Header.php +++ b/src/PhpWord/Writer/Word2007/Part/Header.php @@ -1,4 +1,5 @@ writeAttribute('w:val', $style->getType()); $xmlWriter->endElement(); // w:multiLevelType - if (is_array($levels)) { - foreach ($levels as $level) { - $this->writeLevel($xmlWriter, $level); - } + foreach ($levels as $level) { + $this->writeLevel($xmlWriter, $level); } $xmlWriter->endElement(); // w:abstractNum } @@ -96,29 +95,26 @@ public function write() /** * Write level. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\NumberingLevel $level - * @return void */ - private function writeLevel(XMLWriter $xmlWriter, NumberingLevel $level) + private function writeLevel(XMLWriter $xmlWriter, NumberingLevel $level): void { $xmlWriter->startElement('w:lvl'); $xmlWriter->writeAttribute('w:ilvl', $level->getLevel()); // Numbering level properties - $properties = array( - 'start' => 'start', - 'format' => 'numFmt', + $properties = [ + 'start' => 'start', + 'format' => 'numFmt', 'restart' => 'lvlRestart', - 'pStyle' => 'pStyle', - 'suffix' => 'suff', - 'text' => 'lvlText', - 'align' => 'lvlJc' - ); + 'pStyle' => 'pStyle', + 'suffix' => 'suff', + 'text' => 'lvlText', + 'alignment' => 'lvlJc', + ]; foreach ($properties as $property => $nodeName) { $getMethod = "get{$property}"; - if (!is_null($level->$getMethod())) { + if ('' !== $level->$getMethod() // this condition is now supported by `alignment` only + && null !== $level->$getMethod()) { $xmlWriter->startElement("w:{$nodeName}"); $xmlWriter->writeAttribute('w:val', $level->$getMethod()); $xmlWriter->endElement(); // w:start @@ -137,12 +133,9 @@ private function writeLevel(XMLWriter $xmlWriter, NumberingLevel $level) * * @since 0.11.0 * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\NumberingLevel $level - * @return void * @todo Use paragraph style writer */ - private function writeParagraph(XMLWriter $xmlWriter, NumberingLevel $level) + private function writeParagraph(XMLWriter $xmlWriter, NumberingLevel $level): void { $tabPos = $level->getTabPos(); $left = $level->getLeft(); @@ -170,12 +163,9 @@ private function writeParagraph(XMLWriter $xmlWriter, NumberingLevel $level) * * @since 0.11.0 * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\NumberingLevel $level - * @return void * @todo Use font style writer */ - private function writeFont(XMLWriter $xmlWriter, NumberingLevel $level) + private function writeFont(XMLWriter $xmlWriter, NumberingLevel $level): void { $font = $level->getFont(); $hint = $level->getHint(); @@ -191,13 +181,14 @@ private function writeFont(XMLWriter $xmlWriter, NumberingLevel $level) } /** - * Get random hexadecimal number value + * Get random hexadecimal number value. * * @param int $length + * * @return string */ private function getRandomHexNumber($length = 8) { - return strtoupper(substr(md5(rand()), 0, $length)); + return strtoupper((string) substr(md5((string) mt_rand()), 0, $length)); } } diff --git a/src/PhpWord/Writer/Word2007/Part/Rels.php b/src/PhpWord/Writer/Word2007/Part/Rels.php index 9a7f444bd4..f282f80ef4 100644 --- a/src/PhpWord/Writer/Word2007/Part/Rels.php +++ b/src/PhpWord/Writer/Word2007/Part/Rels.php @@ -1,4 +1,5 @@ 'package/2006/relationships/metadata/core-properties', - 'docProps/app.xml' => 'officeDocument/2006/relationships/extended-properties', + $xmlRels = [ + 'docProps/core.xml' => 'package/2006/relationships/metadata/core-properties', + 'docProps/app.xml' => 'officeDocument/2006/relationships/extended-properties', 'docProps/custom.xml' => 'officeDocument/2006/relationships/custom-properties', - 'word/document.xml' => 'officeDocument/2006/relationships/officeDocument', - ); + 'word/document.xml' => 'officeDocument/2006/relationships/officeDocument', + ]; $xmlWriter = $this->getXmlWriter(); $this->writeRels($xmlWriter, $xmlRels); @@ -49,13 +50,11 @@ public function write() /** * Write relationships. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param array $xmlRels * @param array $mediaRels * @param int $relId - * @return void */ - protected function writeRels(XMLWriter $xmlWriter, $xmlRels = array(), $mediaRels = array(), $relId = 1) + protected function writeRels(XMLWriter $xmlWriter, $xmlRels = [], $mediaRels = [], $relId = 1): void { $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); $xmlWriter->startElement('Relationships'); @@ -77,20 +76,18 @@ protected function writeRels(XMLWriter $xmlWriter, $xmlRels = array(), $mediaRel /** * Write media relationships. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param int $relId * @param array $mediaRel - * @return void */ - private function writeMediaRel(XMLWriter $xmlWriter, $relId, $mediaRel) + private function writeMediaRel(XMLWriter $xmlWriter, $relId, $mediaRel): void { $typePrefix = 'officeDocument/2006/relationships/'; - $typeMapping = array('image' => 'image', 'object' => 'oleObject', 'link' => 'hyperlink'); - $targetMapping = array('image' => 'media/', 'object' => 'embeddings/'); + $typeMapping = ['image' => 'image', 'object' => 'oleObject', 'link' => 'hyperlink']; + $targetMapping = ['image' => 'media/', 'object' => 'embeddings/']; $mediaType = $mediaRel['type']; - $type = isset($typeMapping[$mediaType]) ? $typeMapping[$mediaType] : $mediaType; - $targetPrefix = isset($targetMapping[$mediaType]) ? $targetMapping[$mediaType] : ''; + $type = $typeMapping[$mediaType] ?? $mediaType; + $targetPrefix = $targetMapping[$mediaType] ?? ''; $target = $mediaRel['target']; $targetMode = ($type == 'hyperlink') ? 'External' : ''; @@ -103,15 +100,12 @@ private function writeMediaRel(XMLWriter $xmlWriter, $relId, $mediaRel) * Format: * * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param int $relId Relationship ID * @param string $type Relationship type * @param string $target Relationship target * @param string $targetMode Relationship target mode - * @return void - * @throws \PhpOffice\PhpWord\Exception\Exception */ - private function writeRel(XMLWriter $xmlWriter, $relId, $type, $target, $targetMode = '') + private function writeRel(XMLWriter $xmlWriter, $relId, $type, $target, $targetMode = ''): void { if ($type != '' && $target != '') { if (strpos($relId, 'rId') === false) { @@ -126,7 +120,7 @@ private function writeRel(XMLWriter $xmlWriter, $relId, $type, $target, $targetM } $xmlWriter->endElement(); } else { - throw new Exception("Invalid parameters passed."); + throw new Exception('Invalid parameters passed.'); } } } diff --git a/src/PhpWord/Writer/Word2007/Part/RelsDocument.php b/src/PhpWord/Writer/Word2007/Part/RelsDocument.php index 744e14f9a2..d2a9f994a8 100644 --- a/src/PhpWord/Writer/Word2007/Part/RelsDocument.php +++ b/src/PhpWord/Writer/Word2007/Part/RelsDocument.php @@ -1,4 +1,5 @@ 'officeDocument/2006/relationships/styles', - 'numbering.xml' => 'officeDocument/2006/relationships/numbering', - 'settings.xml' => 'officeDocument/2006/relationships/settings', + $xmlRels = [ + 'styles.xml' => 'officeDocument/2006/relationships/styles', + 'numbering.xml' => 'officeDocument/2006/relationships/numbering', + 'settings.xml' => 'officeDocument/2006/relationships/settings', 'theme/theme1.xml' => 'officeDocument/2006/relationships/theme', - 'webSettings.xml' => 'officeDocument/2006/relationships/webSettings', - 'fontTable.xml' => 'officeDocument/2006/relationships/fontTable', - ); + 'webSettings.xml' => 'officeDocument/2006/relationships/webSettings', + 'fontTable.xml' => 'officeDocument/2006/relationships/fontTable', + ]; $xmlWriter = $this->getXmlWriter(); /** @var \PhpOffice\PhpWord\Writer\Word2007 $parentWriter Type hint */ diff --git a/src/PhpWord/Writer/Word2007/Part/RelsPart.php b/src/PhpWord/Writer/Word2007/Part/RelsPart.php index 627a2bcd1b..5e73c1f28d 100644 --- a/src/PhpWord/Writer/Word2007/Part/RelsPart.php +++ b/src/PhpWord/Writer/Word2007/Part/RelsPart.php @@ -1,4 +1,5 @@ getXmlWriter(); - $this->writeRels($xmlWriter, array(), $this->media); + $this->writeRels($xmlWriter, [], $this->media); return $xmlWriter->getData(); } /** - * Set media + * Set media. * * @param array $media + * * @return self */ public function setMedia($media) diff --git a/src/PhpWord/Writer/Word2007/Part/Settings.php b/src/PhpWord/Writer/Word2007/Part/Settings.php index 50399c4aa0..cd7beb4e7b 100644 --- a/src/PhpWord/Writer/Word2007/Part/Settings.php +++ b/src/PhpWord/Writer/Word2007/Part/Settings.php @@ -1,4 +1,5 @@ writeElement($settingKey); - } else { + } elseif (is_array($settingValue) && !empty($settingValue)) { $xmlWriter->startElement($settingKey); /** @var array $settingValue Type hint */ @@ -92,36 +97,35 @@ protected function writeSetting($xmlWriter, $settingKey, $settingValue) /** * Get settings. - * - * @return void */ - private function getSettings() + private function getSettings(): void { + /** @var \PhpOffice\PhpWord\Metadata\Settings $documentSettings */ + $documentSettings = $this->getParentWriter()->getPhpWord()->getSettings(); + // Default settings - $this->settings = array( - 'w:zoom' => array('@attributes' => array('w:percent' => '100')), - 'w:defaultTabStop' => array('@attributes' => array('w:val' => '708')), - 'w:hyphenationZone' => array('@attributes' => array('w:val' => '425')), - 'w:characterSpacingControl' => array('@attributes' => array('w:val' => 'doNotCompress')), - 'w:themeFontLang' => array('@attributes' => array('w:val' => 'en-US')), - 'w:decimalSymbol' => array('@attributes' => array('w:val' => '.')), - 'w:listSeparator' => array('@attributes' => array('w:val' => ';')), - 'w:compat' => '', - 'm:mathPr' => array( - 'm:mathFont' => array('@attributes' => array('m:val' => 'Cambria Math')), - 'm:brkBin' => array('@attributes' => array('m:val' => 'before')), - 'm:brkBinSub' => array('@attributes' => array('m:val' => '--')), - 'm:smallFrac' => array('@attributes' => array('m:val' => 'off')), + $this->settings = [ + 'w:defaultTabStop' => ['@attributes' => ['w:val' => '708']], + 'w:hyphenationZone' => ['@attributes' => ['w:val' => '425']], + 'w:characterSpacingControl' => ['@attributes' => ['w:val' => 'doNotCompress']], + 'w:decimalSymbol' => ['@attributes' => ['w:val' => $documentSettings->getDecimalSymbol()]], + 'w:listSeparator' => ['@attributes' => ['w:val' => ';']], + 'w:compat' => [], + 'm:mathPr' => [ + 'm:mathFont' => ['@attributes' => ['m:val' => 'Cambria Math']], + 'm:brkBin' => ['@attributes' => ['m:val' => 'before']], + 'm:brkBinSub' => ['@attributes' => ['m:val' => '--']], + 'm:smallFrac' => ['@attributes' => ['m:val' => 'off']], 'm:dispDef' => '', - 'm:lMargin' => array('@attributes' => array('m:val' => '0')), - 'm:rMargin' => array('@attributes' => array('m:val' => '0')), - 'm:defJc' => array('@attributes' => array('m:val' => 'centerGroup')), - 'm:wrapIndent' => array('@attributes' => array('m:val' => '1440')), - 'm:intLim' => array('@attributes' => array('m:val' => 'subSup')), - 'm:naryLim' => array('@attributes' => array('m:val' => 'undOvr')), - ), - 'w:clrSchemeMapping' => array( - '@attributes' => array( + 'm:lMargin' => ['@attributes' => ['m:val' => '0']], + 'm:rMargin' => ['@attributes' => ['m:val' => '0']], + 'm:defJc' => ['@attributes' => ['m:val' => 'centerGroup']], + 'm:wrapIndent' => ['@attributes' => ['m:val' => '1440']], + 'm:intLim' => ['@attributes' => ['m:val' => 'subSup']], + 'm:naryLim' => ['@attributes' => ['m:val' => 'undOvr']], + ], + 'w:clrSchemeMapping' => [ + '@attributes' => [ 'w:bg1' => 'light1', 'w:t1' => 'dark1', 'w:bg2' => 'light2', @@ -134,47 +138,187 @@ private function getSettings() 'w:accent6' => 'accent6', 'w:hyperlink' => 'hyperlink', 'w:followedHyperlink' => 'followedHyperlink', - ), - ), - ); + ], + ], + ]; + + $this->setOnOffValue('w:mirrorMargins', $documentSettings->hasMirrorMargins()); + $this->setOnOffValue('w:hideSpellingErrors', $documentSettings->hasHideSpellingErrors()); + $this->setOnOffValue('w:hideGrammaticalErrors', $documentSettings->hasHideGrammaticalErrors()); + $this->setOnOffValue('w:trackRevisions', $documentSettings->hasTrackRevisions()); + $this->setOnOffValue('w:doNotTrackMoves', $documentSettings->hasDoNotTrackMoves()); + $this->setOnOffValue('w:doNotTrackFormatting', $documentSettings->hasDoNotTrackFormatting()); + $this->setOnOffValue('w:evenAndOddHeaders', $documentSettings->hasEvenAndOddHeaders()); + $this->setOnOffValue('w:updateFields', $documentSettings->hasUpdateFields()); + $this->setOnOffValue('w:autoHyphenation', $documentSettings->hasAutoHyphenation()); + $this->setOnOffValue('w:doNotHyphenateCaps', $documentSettings->hasDoNotHyphenateCaps()); + $this->setOnOffValue('w:bookFoldPrinting', $documentSettings->hasBookFoldPrinting()); - // Other settings - $this->getProtection(); - $this->getCompatibility(); + $this->setThemeFontLang($documentSettings->getThemeFontLang()); + $this->setRevisionView($documentSettings->getRevisionView()); + $this->setDocumentProtection($documentSettings->getDocumentProtection()); + $this->setProofState($documentSettings->getProofState()); + $this->setZoom($documentSettings->getZoom()); + $this->setConsecutiveHyphenLimit($documentSettings->getConsecutiveHyphenLimit()); + $this->setHyphenationZone($documentSettings->getHyphenationZone()); + $this->setCompatibility(); + } + + /** + * Adds a boolean attribute to the settings array. + * + * @param string $settingName + * @param null|bool $booleanValue + */ + private function setOnOffValue($settingName, $booleanValue): void + { + if (!is_bool($booleanValue)) { + return; + } + + $value = $booleanValue ? 'true' : 'false'; + $this->settings[$settingName] = ['@attributes' => ['w:val' => $value]]; } /** * Get protection settings. * - * @return void + * @param \PhpOffice\PhpWord\Metadata\Protection $documentProtection */ - private function getProtection() + private function setDocumentProtection($documentProtection): void { - $protection = $this->getParentWriter()->getPhpWord()->getProtection(); - if ($protection->getEditing() !== null) { - $this->settings['w:documentProtection'] = array( - '@attributes' => array( - 'w:enforcement' => 1, - 'w:edit' => $protection->getEditing(), - ) - ); + if ($documentProtection->getEditing() !== null) { + if ($documentProtection->getPassword() == null) { + $this->settings['w:documentProtection'] = [ + '@attributes' => [ + 'w:enforcement' => 1, + 'w:edit' => $documentProtection->getEditing(), + ], + ]; + } else { + if ($documentProtection->getSalt() == null) { + $documentProtection->setSalt((string) openssl_random_pseudo_bytes(16)); + } + $passwordHash = PasswordEncoder::hashPassword($documentProtection->getPassword(), $documentProtection->getAlgorithm(), $documentProtection->getSalt(), $documentProtection->getSpinCount()); + $this->settings['w:documentProtection'] = [ + '@attributes' => [ + 'w:enforcement' => 1, + 'w:edit' => $documentProtection->getEditing(), + 'w:cryptProviderType' => 'rsaFull', + 'w:cryptAlgorithmClass' => 'hash', + 'w:cryptAlgorithmType' => 'typeAny', + 'w:cryptAlgorithmSid' => PasswordEncoder::getAlgorithmId($documentProtection->getAlgorithm()), + 'w:cryptSpinCount' => $documentProtection->getSpinCount(), + 'w:hash' => $passwordHash, + 'w:salt' => base64_encode($documentProtection->getSalt()), + ], + ]; + } } } /** - * Get compatibility setting. + * Set the Proof state. + */ + private function setProofState(?ProofState $proofState = null): void + { + if ($proofState != null && $proofState->getGrammar() !== null && $proofState->getSpelling() !== null) { + $this->settings['w:proofState'] = [ + '@attributes' => [ + 'w:spelling' => $proofState->getSpelling(), + 'w:grammar' => $proofState->getGrammar(), + ], + ]; + } + } + + /** + * Set the Revision View. + */ + private function setRevisionView(?TrackChangesView $trackChangesView = null): void + { + if ($trackChangesView != null) { + $revisionView = []; + $revisionView['w:markup'] = $trackChangesView->hasMarkup() ? 'true' : 'false'; + $revisionView['w:comments'] = $trackChangesView->hasComments() ? 'true' : 'false'; + $revisionView['w:insDel'] = $trackChangesView->hasInsDel() ? 'true' : 'false'; + $revisionView['w:formatting'] = $trackChangesView->hasFormatting() ? 'true' : 'false'; + $revisionView['w:inkAnnotations'] = $trackChangesView->hasInkAnnotations() ? 'true' : 'false'; + + $this->settings['w:revisionView'] = ['@attributes' => $revisionView]; + } + } + + /** + * Sets the language. + */ + private function setThemeFontLang(?Language $language = null): void + { + $latinLanguage = ($language == null || $language->getLatin() === null) ? 'en-US' : $language->getLatin(); + $lang = []; + $lang['w:val'] = $latinLanguage; + if ($language != null) { + $lang['w:eastAsia'] = $language->getEastAsia() === null ? 'x-none' : $language->getEastAsia(); + $lang['w:bidi'] = $language->getBidirectional() === null ? 'x-none' : $language->getBidirectional(); + } + $this->settings['w:themeFontLang'] = ['@attributes' => $lang]; + } + + /** + * Set the magnification. * - * @return void + * @param mixed $zoom + */ + private function setZoom($zoom = null): void + { + if ($zoom !== null) { + $attr = is_int($zoom) ? 'w:percent' : 'w:val'; + $this->settings['w:zoom'] = ['@attributes' => [$attr => $zoom]]; + } + } + + /** + * @param null|int $consecutiveHyphenLimit + */ + private function setConsecutiveHyphenLimit($consecutiveHyphenLimit): void + { + if ($consecutiveHyphenLimit === null) { + return; + } + + $this->settings['w:consecutiveHyphenLimit'] = [ + '@attributes' => ['w:val' => $consecutiveHyphenLimit], + ]; + } + + /** + * @param null|float $hyphenationZone + */ + private function setHyphenationZone($hyphenationZone): void + { + if ($hyphenationZone === null) { + return; + } + + $this->settings['w:hyphenationZone'] = [ + '@attributes' => ['w:val' => $hyphenationZone], + ]; + } + + /** + * Get compatibility setting. */ - private function getCompatibility() + private function setCompatibility(): void { $compatibility = $this->getParentWriter()->getPhpWord()->getCompatibility(); if ($compatibility->getOoxmlVersion() !== null) { - $this->settings['w:compat']['w:compatSetting'] = array('@attributes' => array( - 'w:name' => 'compatibilityMode', - 'w:uri' => 'http://schemas.microsoft.com/office/word', - 'w:val' => $compatibility->getOoxmlVersion(), - )); + $this->settings['w:compat']['w:compatSetting'] = [ + '@attributes' => [ + 'w:name' => 'compatibilityMode', + 'w:uri' => 'http://schemas.microsoft.com/office/word', + 'w:val' => $compatibility->getOoxmlVersion(), + ], + ]; } } } diff --git a/src/PhpWord/Writer/Word2007/Part/Styles.php b/src/PhpWord/Writer/Word2007/Part/Styles.php index 0194222948..0ed9d6b6fb 100644 --- a/src/PhpWord/Writer/Word2007/Part/Styles.php +++ b/src/PhpWord/Writer/Word2007/Part/Styles.php @@ -1,4 +1,5 @@ getParentWriter()->getPhpWord(); + $fontName = $phpWord->getDefaultFontName(); + $asianFontName = $phpWord->getDefaultAsianFontName(); + $fontSize = $phpWord->getDefaultFontSize(); + $fontColor = $phpWord->getDefaultFontColor(); + $language = $phpWord->getSettings()->getThemeFontLang(); + $latinLanguage = ($language == null || $language->getLatin() === null) ? 'en-US' : $language->getLatin(); // Default font $xmlWriter->startElement('w:docDefaults'); @@ -93,15 +97,25 @@ private function writeDefaultStyles(XMLWriter $xmlWriter, $styles) $xmlWriter->startElement('w:rFonts'); $xmlWriter->writeAttribute('w:ascii', $fontName); $xmlWriter->writeAttribute('w:hAnsi', $fontName); - $xmlWriter->writeAttribute('w:eastAsia', $fontName); + $xmlWriter->writeAttribute('w:eastAsia', $asianFontName); $xmlWriter->writeAttribute('w:cs', $fontName); $xmlWriter->endElement(); // w:rFonts + $xmlWriter->startElement('w:color'); + $xmlWriter->writeAttribute('w:val', $fontColor); + $xmlWriter->endElement(); $xmlWriter->startElement('w:sz'); $xmlWriter->writeAttribute('w:val', $fontSize * 2); $xmlWriter->endElement(); // w:sz $xmlWriter->startElement('w:szCs'); $xmlWriter->writeAttribute('w:val', $fontSize * 2); $xmlWriter->endElement(); // w:szCs + $xmlWriter->startElement('w:lang'); + $xmlWriter->writeAttribute('w:val', $latinLanguage); + if ($language != null) { + $xmlWriter->writeAttributeIf($language->getEastAsia() !== null, 'w:eastAsia', $language->getEastAsia()); + $xmlWriter->writeAttributeIf($language->getBidirectional() !== null, 'w:bidi', $language->getBidirectional()); + } + $xmlWriter->endElement(); // w:lang $xmlWriter->endElement(); // w:rPr $xmlWriter->endElement(); // w:rPrDefault $xmlWriter->endElement(); // w:docDefaults @@ -115,7 +129,18 @@ private function writeDefaultStyles(XMLWriter $xmlWriter, $styles) $xmlWriter->writeAttribute('w:val', 'Normal'); $xmlWriter->endElement(); // w:name if (isset($styles['Normal'])) { - $styleWriter = new ParagraphStyleWriter($xmlWriter, $styles['Normal']); + $normalStyle = $styles['Normal']; + // w:pPr + if ($normalStyle instanceof FontStyle && $normalStyle->getParagraph() != null) { + $styleWriter = new ParagraphStyleWriter($xmlWriter, $normalStyle->getParagraph()); + $styleWriter->write(); + } elseif ($normalStyle instanceof ParagraphStyle) { + $styleWriter = new ParagraphStyleWriter($xmlWriter, $normalStyle); + $styleWriter->write(); + } + + // w:rPr + $styleWriter = new FontStyleWriter($xmlWriter, $normalStyle); $styleWriter->write(); } $xmlWriter->endElement(); // w:style @@ -142,17 +167,14 @@ private function writeDefaultStyles(XMLWriter $xmlWriter, $styles) /** * Write font style. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param string $styleName - * @param \PhpOffice\PhpWord\Style\Font $style - * @return void */ - private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $style) + private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $style): void { $paragraphStyle = $style->getParagraph(); $styleType = $style->getStyleType(); $type = ($styleType == 'title') ? 'paragraph' : 'character'; - if (!is_null($paragraphStyle)) { + if (null !== $paragraphStyle) { $type = 'paragraph'; } @@ -162,14 +184,23 @@ private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $sty // Heading style if ($styleType == 'title') { $arrStyle = explode('_', $styleName); - $styleId = 'Heading' . $arrStyle[1]; - $styleName = 'heading ' . $arrStyle[1]; - $styleLink = 'Heading' . $arrStyle[1] . 'Char'; + if (count($arrStyle) > 1) { + $styleId = 'Heading' . $arrStyle[1]; + $styleName = 'heading ' . $arrStyle[1]; + $styleLink = 'Heading' . $arrStyle[1] . 'Char'; + } else { + $styleId = $styleName; + $styleName = strtolower($styleName); + $styleLink = $styleName . 'Char'; + } $xmlWriter->writeAttribute('w:styleId', $styleId); $xmlWriter->startElement('w:link'); $xmlWriter->writeAttribute('w:val', $styleLink); $xmlWriter->endElement(); + } elseif (null !== $paragraphStyle) { + // if type is 'paragraph' it should have a styleId + $xmlWriter->writeAttribute('w:styleId', $styleName); } // Style name @@ -178,10 +209,16 @@ private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $sty $xmlWriter->endElement(); // Parent style - $xmlWriter->writeElementIf(!is_null($paragraphStyle), 'w:basedOn', 'w:val', 'Normal'); + if (null !== $paragraphStyle) { + if ($paragraphStyle->getStyleName() != null) { + $xmlWriter->writeElementBlock('w:basedOn', 'w:val', $paragraphStyle->getStyleName()); + } elseif ($paragraphStyle->getBasedOn() != null) { + $xmlWriter->writeElementBlock('w:basedOn', 'w:val', $paragraphStyle->getBasedOn()); + } + } // w:pPr - if (!is_null($paragraphStyle)) { + if (null !== $paragraphStyle) { $styleWriter = new ParagraphStyleWriter($xmlWriter, $paragraphStyle); $styleWriter->write(); } @@ -196,12 +233,9 @@ private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $sty /** * Write paragraph style. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param string $styleName - * @param \PhpOffice\PhpWord\Style\Paragraph $style - * @return void */ - private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, ParagraphStyle $style) + private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, ParagraphStyle $style): void { $xmlWriter->startElement('w:style'); $xmlWriter->writeAttribute('w:type', 'paragraph'); @@ -213,11 +247,11 @@ private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, Paragraph // Parent style $basedOn = $style->getBasedOn(); - $xmlWriter->writeElementIf(!is_null($basedOn), 'w:basedOn', 'w:val', $basedOn); + $xmlWriter->writeElementIf(null !== $basedOn, 'w:basedOn', 'w:val', $basedOn); // Next paragraph style $next = $style->getNext(); - $xmlWriter->writeElementIf(!is_null($next), 'w:next', 'w:val', $next); + $xmlWriter->writeElementIf(null !== $next, 'w:next', 'w:val', $next); // w:pPr $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); @@ -229,12 +263,9 @@ private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, Paragraph /** * Write table style. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param string $styleName - * @param \PhpOffice\PhpWord\Style\Table $style - * @return void */ - private function writeTableStyle(XMLWriter $xmlWriter, $styleName, TableStyle $style) + private function writeTableStyle(XMLWriter $xmlWriter, $styleName, TableStyle $style): void { $xmlWriter->startElement('w:style'); $xmlWriter->writeAttribute('w:type', 'table'); diff --git a/src/PhpWord/Writer/Word2007/Part/Theme.php b/src/PhpWord/Writer/Word2007/Part/Theme.php index 62911c548d..a70c248da6 100644 --- a/src/PhpWord/Writer/Word2007/Part/Theme.php +++ b/src/PhpWord/Writer/Word2007/Part/Theme.php @@ -1,4 +1,5 @@ '', - ); + ]; $xmlWriter = $this->getXmlWriter(); diff --git a/src/PhpWord/Writer/Word2007/Style/AbstractStyle.php b/src/PhpWord/Writer/Word2007/Style/AbstractStyle.php index 16335680f0..9a30bebf0e 100644 --- a/src/PhpWord/Writer/Word2007/Style/AbstractStyle.php +++ b/src/PhpWord/Writer/Word2007/Style/AbstractStyle.php @@ -1,4 +1,5 @@ 567, Settings::UNIT_MM => 56.7, Settings::UNIT_INCH => 1440, Settings::UNIT_POINT => 20, Settings::UNIT_PICA => 240, - ); + ]; $unit = Settings::getMeasurementUnit(); $factor = 1; - if (in_array($unit, $factors) && $value != $default) { + if (array_key_exists($unit, $factors) && $value != $default) { $factor = $factors[$unit]; } @@ -106,33 +107,48 @@ protected function convertTwip($value, $default = 0) /** * Write child style. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param string $name * @param mixed $value - * @return void */ - protected function writeChildStyle(XMLWriter $xmlWriter, $name, $value) + protected function writeChildStyle(XMLWriter $xmlWriter, $name, $value): void { if ($value !== null) { - $class = "PhpOffice\\PhpWord\\Writer\\Word2007\\Style\\" . $name; + $class = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Style\\' . $name; - /** @var \PhpOffice\PhpWord\Writer\Word2007\Style\AbstractStyle $writer */ + /** @var AbstractStyle $writer */ $writer = new $class($xmlWriter, $value); $writer->write(); } } /** - * Assemble style array into style string + * Writes boolean as 0 or 1. + * + * @param bool $value + * + * @return null|string + */ + protected function writeOnOf($value = null) + { + if ($value === null) { + return null; + } + + return $value ? '1' : '0'; + } + + /** + * Assemble style array into style string. * * @param array $styles + * * @return string */ - protected function assembleStyle($styles = array()) + protected function assembleStyle($styles = []) { $style = ''; foreach ($styles as $key => $value) { - if (!is_null($value) && $value != '') { + if (null !== $value && $value != '') { $style .= "{$key}:{$value}; "; } } diff --git a/src/PhpWord/Writer/Word2007/Style/Cell.php b/src/PhpWord/Writer/Word2007/Style/Cell.php index 0f90b3aa91..bb0d6d71b0 100644 --- a/src/PhpWord/Writer/Word2007/Style/Cell.php +++ b/src/PhpWord/Writer/Word2007/Style/Cell.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof CellStyle) { @@ -47,18 +46,56 @@ public function write() $xmlWriter->startElement('w:tcPr'); // Width - $xmlWriter->startElement('w:tcW'); - $xmlWriter->writeAttribute('w:w', $this->width); - $xmlWriter->writeAttribute('w:type', 'dxa'); - $xmlWriter->endElement(); // w:tcW + if (null !== $this->width || null !== $style->getWidth()) { + $width = null === $this->width ? $style->getWidth() : $this->width; + + $xmlWriter->startElement('w:tcW'); + $xmlWriter->writeAttribute('w:w', $width); + $xmlWriter->writeAttribute('w:type', $style->getUnit()); + $xmlWriter->endElement(); // w:tcW + } + + $paddingTop = $style->getPaddingTop(); + $paddingLeft = $style->getPaddingLeft(); + $paddingBottom = $style->getPaddingBottom(); + $paddingRight = $style->getPaddingRight(); + + if ($paddingTop !== null || $paddingLeft !== null || $paddingBottom !== null || $paddingRight !== null) { + $xmlWriter->startElement('w:tcMar'); + if ($paddingTop !== null) { + $xmlWriter->startElement('w:top'); + $xmlWriter->writeAttribute('w:w', $paddingTop); + $xmlWriter->writeAttribute('w:type', \PhpOffice\PhpWord\SimpleType\TblWidth::TWIP); + $xmlWriter->endElement(); // w:top + } + if ($paddingLeft !== null) { + $xmlWriter->startElement('w:start'); + $xmlWriter->writeAttribute('w:w', $paddingLeft); + $xmlWriter->writeAttribute('w:type', \PhpOffice\PhpWord\SimpleType\TblWidth::TWIP); + $xmlWriter->endElement(); // w:start + } + if ($paddingBottom !== null) { + $xmlWriter->startElement('w:bottom'); + $xmlWriter->writeAttribute('w:w', $paddingBottom); + $xmlWriter->writeAttribute('w:type', \PhpOffice\PhpWord\SimpleType\TblWidth::TWIP); + $xmlWriter->endElement(); // w:bottom + } + if ($paddingRight !== null) { + $xmlWriter->startElement('w:end'); + $xmlWriter->writeAttribute('w:w', $paddingRight); + $xmlWriter->writeAttribute('w:type', \PhpOffice\PhpWord\SimpleType\TblWidth::TWIP); + $xmlWriter->endElement(); // w:end + } + $xmlWriter->endElement(); // w:tcMar + } // Text direction $textDir = $style->getTextDirection(); - $xmlWriter->writeElementIf(!is_null($textDir), 'w:textDirection', 'w:val', $textDir); + $xmlWriter->writeElementIf(null !== $textDir, 'w:textDirection', 'w:val', $textDir); // Vertical alignment $vAlign = $style->getVAlign(); - $xmlWriter->writeElementIf(!is_null($vAlign), 'w:vAlign', 'w:val', $vAlign); + $xmlWriter->writeElementIf(null !== $vAlign, 'w:vAlign', 'w:val', $vAlign); // Border if ($style->hasBorder()) { @@ -67,7 +104,8 @@ public function write() $styleWriter = new MarginBorder($xmlWriter); $styleWriter->setSizes($style->getBorderSize()); $styleWriter->setColors($style->getBorderColor()); - $styleWriter->setAttributes(array('defaultColor' => CellStyle::DEFAULT_BORDER_COLOR)); + $styleWriter->setStyles($style->getBorderStyle()); + $styleWriter->setAttributes(['defaultColor' => CellStyle::DEFAULT_BORDER_COLOR]); $styleWriter->write(); $xmlWriter->endElement(); @@ -75,7 +113,7 @@ public function write() // Shading $shading = $style->getShading(); - if (!is_null($shading)) { + if (null !== $shading) { $styleWriter = new Shading($xmlWriter, $shading); $styleWriter->write(); } @@ -83,8 +121,9 @@ public function write() // Colspan & rowspan $gridSpan = $style->getGridSpan(); $vMerge = $style->getVMerge(); - $xmlWriter->writeElementIf(!is_null($gridSpan), 'w:gridSpan', 'w:val', $gridSpan); - $xmlWriter->writeElementIf(!is_null($vMerge), 'w:vMerge', 'w:val', $vMerge); + $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 } @@ -93,9 +132,8 @@ public function write() * Set width. * * @param int $value - * @return void */ - public function setWidth($value = null) + public function setWidth($value = null): void { $this->width = $value; } diff --git a/src/PhpWord/Writer/Word2007/Style/Extrusion.php b/src/PhpWord/Writer/Word2007/Style/Extrusion.php index ba1ee590ec..8bb9218789 100644 --- a/src/PhpWord/Writer/Word2007/Style/Extrusion.php +++ b/src/PhpWord/Writer/Word2007/Style/Extrusion.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Extrusion) { @@ -37,7 +36,7 @@ public function write() } $xmlWriter = $this->getXmlWriter(); - $xmlWriter->startElement("o:extrusion"); + $xmlWriter->startElement('o:extrusion'); $xmlWriter->writeAttribute('on', 't'); $xmlWriter->writeAttributeIf($style->getType() !== null, 'type', $style->getType()); $xmlWriter->writeAttributeIf($style->getColor() !== null, 'color', $style->getColor()); diff --git a/src/PhpWord/Writer/Word2007/Style/Fill.php b/src/PhpWord/Writer/Word2007/Style/Fill.php index 645f1ab053..8e21abe395 100644 --- a/src/PhpWord/Writer/Word2007/Style/Fill.php +++ b/src/PhpWord/Writer/Word2007/Style/Fill.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Fill) { diff --git a/src/PhpWord/Writer/Word2007/Style/Font.php b/src/PhpWord/Writer/Word2007/Style/Font.php index 9371f97003..623e8d5e72 100644 --- a/src/PhpWord/Writer/Word2007/Style/Font.php +++ b/src/PhpWord/Writer/Word2007/Style/Font.php @@ -1,4 +1,5 @@ getXmlWriter(); - $isStyleName = $this->isInline && !is_null($this->style) && is_string($this->style); + $isStyleName = $this->isInline && null !== $this->style && is_string($this->style); if ($isStyleName) { $xmlWriter->startElement('w:rPr'); $xmlWriter->startElement('w:rStyle'); $xmlWriter->writeAttribute('w:val', $this->style); $xmlWriter->endElement(); + $style = \PhpOffice\PhpWord\Style::getStyle($this->style); + if ($style instanceof \PhpOffice\PhpWord\Style\Font) { + $xmlWriter->writeElementIf($style->isRTL(), 'w:rtl'); + } $xmlWriter->endElement(); } else { $this->writeStyle(); @@ -54,15 +57,14 @@ public function write() /** * Write full style. - * - * @return void */ - private function writeStyle() + private function writeStyle(): void { $style = $this->getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Font) { return; } + $xmlWriter = $this->getXmlWriter(); $xmlWriter->startElement('w:rPr'); @@ -86,6 +88,20 @@ private function writeStyle() $xmlWriter->endElement(); } + //Language + $language = $style->getLang(); + if ($language != null && ($language->getLatin() !== null || $language->getEastAsia() !== null || $language->getBidirectional() !== null)) { + $xmlWriter->startElement('w:lang'); + $xmlWriter->writeAttributeIf($language->getLatin() !== null, 'w:val', $language->getLatin()); + $xmlWriter->writeAttributeIf($language->getEastAsia() !== null, 'w:eastAsia', $language->getEastAsia()); + $xmlWriter->writeAttributeIf($language->getBidirectional() !== null, 'w:bidi', $language->getBidirectional()); + //if bidi is not set but we are writing RTL, write the latin language in the bidi tag + if ($style->isRTL() && $language->getBidirectional() === null && $language->getLatin() !== null) { + $xmlWriter->writeAttribute('w:bidi', $language->getLatin()); + } + $xmlWriter->endElement(); + } + // Color $color = $style->getColor(); $xmlWriter->writeElementIf($color !== null, 'w:color', 'w:val', $color); @@ -96,17 +112,21 @@ private function writeStyle() $xmlWriter->writeElementIf($size !== null, 'w:szCs', 'w:val', $size * 2); // Bold, italic - $xmlWriter->writeElementIf($style->isBold(), 'w:b'); - $xmlWriter->writeElementIf($style->isItalic(), 'w:i'); - $xmlWriter->writeElementIf($style->isItalic(), 'w:iCs'); + $xmlWriter->writeElementIf($style->isBold() !== null, 'w:b', 'w:val', $this->writeOnOf($style->isBold())); + $xmlWriter->writeElementIf($style->isBold() !== null, 'w:bCs', 'w:val', $this->writeOnOf($style->isBold())); + $xmlWriter->writeElementIf($style->isItalic() !== null, 'w:i', 'w:val', $this->writeOnOf($style->isItalic())); + $xmlWriter->writeElementIf($style->isItalic() !== null, 'w:iCs', 'w:val', $this->writeOnOf($style->isItalic())); // Strikethrough, double strikethrough - $xmlWriter->writeElementIf($style->isStrikethrough(), 'w:strike'); - $xmlWriter->writeElementIf($style->isDoubleStrikethrough(), 'w:dstrike'); + $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(), 'w:smallCaps'); - $xmlWriter->writeElementIf($style->isAllCaps(), 'w:caps'); + $xmlWriter->writeElementIf($style->isSmallCaps() !== null, 'w:smallCaps', 'w:val', $this->writeOnOf($style->isSmallCaps())); + $xmlWriter->writeElementIf($style->isAllCaps() !== null, 'w:caps', 'w:val', $this->writeOnOf($style->isAllCaps())); + + //Hidden text + $xmlWriter->writeElementIf($style->isHidden(), 'w:vanish', 'w:val', $this->writeOnOf($style->isHidden())); // Underline $xmlWriter->writeElementIf($style->getUnderline() != 'none', 'w:u', 'w:val', $style->getUnderline()); @@ -123,19 +143,25 @@ private function writeStyle() $xmlWriter->writeElementIf($style->getSpacing() !== null, 'w:spacing', 'w:val', $style->getSpacing()); $xmlWriter->writeElementIf($style->getKerning() !== null, 'w:kern', 'w:val', $style->getKerning() * 2); + // noProof + $xmlWriter->writeElementIf($style->isNoProof() !== null, 'w:noProof', 'w:val', $this->writeOnOf($style->isNoProof())); + // Background-Color $shading = $style->getShading(); - if (!is_null($shading)) { + if (null !== $shading) { $styleWriter = new Shading($xmlWriter, $shading); $styleWriter->write(); } - + // RTL if ($this->isInline === true) { $styleName = $style->getStyleName(); $xmlWriter->writeElementIf($styleName === null && $style->isRTL(), 'w:rtl'); } + // Position + $xmlWriter->writeElementIf($style->getPosition() !== null, 'w:position', 'w:val', $style->getPosition()); + $xmlWriter->endElement(); } @@ -143,9 +169,8 @@ private function writeStyle() * Set is inline. * * @param bool $value - * @return void */ - public function setIsInline($value) + public function setIsInline($value): void { $this->isInline = $value; } diff --git a/src/PhpWord/Writer/Word2007/Style/Frame.php b/src/PhpWord/Writer/Word2007/Style/Frame.php index 28ebeaf52e..a7aab43e61 100644 --- a/src/PhpWord/Writer/Word2007/Style/Frame.php +++ b/src/PhpWord/Writer/Word2007/Style/Frame.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof FrameStyle) { @@ -41,28 +42,33 @@ public function write() } $xmlWriter = $this->getXmlWriter(); - $zIndices = array(FrameStyle::WRAP_INFRONT => PHP_INT_MAX, FrameStyle::WRAP_BEHIND => -PHP_INT_MAX); - - $properties = array( - 'width' => 'width', - 'height' => 'height', - 'left' => 'margin-left', - 'top' => 'margin-top', - ); + $maxZIndex = min(PHP_INT_MAX, self::PHP_32BIT_INT_MAX); + $zIndices = [FrameStyle::WRAP_INFRONT => $maxZIndex, FrameStyle::WRAP_BEHIND => -$maxZIndex]; + + $properties = [ + 'width' => 'width', + 'height' => 'height', + 'left' => 'margin-left', + 'top' => 'margin-top', + 'wrapDistanceTop' => 'mso-wrap-distance-top', + 'wrapDistanceBottom' => 'mso-wrap-distance-bottom', + 'wrapDistanceLeft' => 'mso-wrap-distance-left', + 'wrapDistanceRight' => 'mso-wrap-distance-right', + ]; $sizeStyles = $this->getStyles($style, $properties, $style->getUnit()); - $properties = array( - 'pos' => 'position', - 'hPos' => 'mso-position-horizontal', - 'vPos' => 'mso-position-vertical', + $properties = [ + 'pos' => 'position', + 'hPos' => 'mso-position-horizontal', + 'vPos' => 'mso-position-vertical', 'hPosRelTo' => 'mso-position-horizontal-relative', 'vPosRelTo' => 'mso-position-vertical-relative', - ); + ]; $posStyles = $this->getStyles($style, $properties); $styles = array_merge($sizeStyles, $posStyles); - // zIndex for infront & behind wrap + // zIndex for infront & behind wrap $wrap = $style->getWrap(); if ($wrap !== null && isset($zIndices[$wrap])) { $styles['z-index'] = $zIndices[$wrap]; @@ -77,10 +83,8 @@ public function write() /** * Write alignment. - * - * @return void */ - public function writeAlignment() + public function writeAlignment(): void { $style = $this->getStyle(); if (!$style instanceof FrameStyle) { @@ -89,40 +93,45 @@ public function writeAlignment() $xmlWriter = $this->getXmlWriter(); $xmlWriter->startElement('w:pPr'); - $styleWriter = new Alignment($xmlWriter, new AlignmentStyle(array('value' => $style->getAlign()))); - $styleWriter->write(); - $xmlWriter->endElement(); // w:pPr + + if ('' !== $style->getAlignment()) { + $paragraphAlignment = new ParagraphAlignment($style->getAlignment()); + $xmlWriter->startElement($paragraphAlignment->getName()); + foreach ($paragraphAlignment->getAttributes() as $attributeName => $attributeValue) { + $xmlWriter->writeAttribute($attributeName, $attributeValue); + } + $xmlWriter->endElement(); + } + + $xmlWriter->endElement(); } /** - * Write alignment. + * Write wrap. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Frame $style * @param string $wrap - * @return void */ - private function writeWrap(XMLWriter $xmlWriter, FrameStyle $style, $wrap) + private function writeWrap(XMLWriter $xmlWriter, FrameStyle $style, $wrap): void { if ($wrap !== null) { $xmlWriter->startElement('w10:wrap'); $xmlWriter->writeAttribute('type', $wrap); - $relativePositions = array( - FrameStyle::POS_RELTO_MARGIN => 'margin', - FrameStyle::POS_RELTO_PAGE => 'page', + $relativePositions = [ + FrameStyle::POS_RELTO_MARGIN => 'margin', + FrameStyle::POS_RELTO_PAGE => 'page', FrameStyle::POS_RELTO_TMARGIN => 'margin', FrameStyle::POS_RELTO_BMARGIN => 'page', FrameStyle::POS_RELTO_LMARGIN => 'margin', FrameStyle::POS_RELTO_RMARGIN => 'page', - ); + ]; $pos = $style->getPos(); $hPos = $style->getHPosRelTo(); $vPos = $style->getVPosRelTo(); if ($pos == FrameStyle::POS_ABSOLUTE) { - $xmlWriter->writeAttribute('anchorx', "page"); - $xmlWriter->writeAttribute('anchory', "page"); + $xmlWriter->writeAttribute('anchorx', 'page'); + $xmlWriter->writeAttribute('anchory', 'page'); } elseif ($pos == FrameStyle::POS_RELATIVE) { if (isset($relativePositions[$hPos])) { $xmlWriter->writeAttribute('anchorx', $relativePositions[$hPos]); @@ -137,16 +146,16 @@ private function writeWrap(XMLWriter $xmlWriter, FrameStyle $style, $wrap) } /** - * Get style values in associative array + * Get style values in associative array. * - * @param \PhpOffice\PhpWord\Style\Frame $style * @param array $properties * @param string $suffix + * * @return array */ private function getStyles(FrameStyle $style, $properties, $suffix = '') { - $styles = array(); + $styles = []; foreach ($properties as $key => $property) { $method = "get{$key}"; diff --git a/src/PhpWord/Writer/Word2007/Style/Image.php b/src/PhpWord/Writer/Word2007/Style/Image.php index cabf37ce3b..a2279e5b9c 100644 --- a/src/PhpWord/Writer/Word2007/Style/Image.php +++ b/src/PhpWord/Writer/Word2007/Style/Image.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Indentation) { @@ -43,10 +42,13 @@ public function write() $xmlWriter->writeAttribute('w:right', $this->convertTwip($style->getRight())); $firstLine = $style->getFirstLine(); - $xmlWriter->writeAttributeIf(!is_null($firstLine), 'w:firstLine', $this->convertTwip($firstLine)); + $xmlWriter->writeAttributeIf(null !== $firstLine, 'w:firstLine', $this->convertTwip($firstLine)); + + $firstLineChars = $style->getFirstLineChars(); + $xmlWriter->writeAttributeIf(0 !== $firstLineChars, 'w:firstLineChars', $this->convertTwip($firstLineChars)); $hanging = $style->getHanging(); - $xmlWriter->writeAttributeIf(!is_null($hanging), 'w:hanging', $this->convertTwip($hanging)); + $xmlWriter->writeAttributeIf(null !== $hanging, 'w:hanging', $this->convertTwip($hanging)); $xmlWriter->endElement(); } diff --git a/src/PhpWord/Writer/Word2007/Style/Line.php b/src/PhpWord/Writer/Word2007/Style/Line.php index 48e27492ac..2603545fc2 100644 --- a/src/PhpWord/Writer/Word2007/Style/Line.php +++ b/src/PhpWord/Writer/Word2007/Style/Line.php @@ -1,4 +1,5 @@ getXmlWriter(); $style = $this->getStyle(); @@ -40,15 +39,15 @@ public function writeStroke() } $dash = $style->getDash(); - $dashStyles = array( - LineStyle::DASH_STYLE_DASH => 'dash', - LineStyle::DASH_STYLE_ROUND_DOT => '1 1', - LineStyle::DASH_STYLE_SQUARE_DOT => '1 1', - LineStyle::DASH_STYLE_DASH_DOT => 'dashDot', - LineStyle::DASH_STYLE_LONG_DASH => 'longDash', - LineStyle::DASH_STYLE_LONG_DASH_DOT => 'longDashDot', + $dashStyles = [ + LineStyle::DASH_STYLE_DASH => 'dash', + LineStyle::DASH_STYLE_ROUND_DOT => '1 1', + LineStyle::DASH_STYLE_SQUARE_DOT => '1 1', + LineStyle::DASH_STYLE_DASH_DOT => 'dashDot', + LineStyle::DASH_STYLE_LONG_DASH => 'longDash', + LineStyle::DASH_STYLE_LONG_DASH_DOT => 'longDashDot', LineStyle::DASH_STYLE_LONG_DASH_DOT_DOT => 'longDashDotDot', - ); + ]; $xmlWriter->startElement('v:stroke'); diff --git a/src/PhpWord/Writer/Word2007/Style/LineNumbering.php b/src/PhpWord/Writer/Word2007/Style/LineNumbering.php index d06fa2d697..f915e3ab4e 100644 --- a/src/PhpWord/Writer/Word2007/Style/LineNumbering.php +++ b/src/PhpWord/Writer/Word2007/Style/LineNumbering.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\LineNumbering) { diff --git a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php index 30029112f8..b464e66ffb 100644 --- a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php +++ b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php @@ -1,4 +1,5 @@ getXmlWriter(); - $sides = array('top', 'left', 'right', 'bottom', 'insideH', 'insideV'); + $sides = ['top', 'left', 'right', 'bottom', 'insideH', 'insideV']; foreach ($this->sizes as $i => $size) { if ($size !== null) { @@ -64,7 +70,8 @@ public function write() if (isset($this->colors[$i])) { $color = $this->colors[$i]; } - $this->writeSide($xmlWriter, $sides[$i], $this->sizes[$i], $color); + $style = $this->styles[$i] ?? 'single'; + $this->writeSide($xmlWriter, $sides[$i], $this->sizes[$i], $color, $style); } } } @@ -72,13 +79,12 @@ public function write() /** * Write side. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param string $side * @param int $width * @param string $color - * @return void + * @param string $borderStyle */ - private function writeSide(XMLWriter $xmlWriter, $side, $width, $color = null) + private function writeSide(XMLWriter $xmlWriter, $side, $width, $color = null, $borderStyle = 'solid'): void { $xmlWriter->startElement('w:' . $side); if (!empty($this->colors)) { @@ -87,9 +93,9 @@ private function writeSide(XMLWriter $xmlWriter, $side, $width, $color = null) $color = $this->attributes['defaultColor']; } } - $xmlWriter->writeAttribute('w:val', 'single'); + $xmlWriter->writeAttribute('w:val', $borderStyle); $xmlWriter->writeAttribute('w:sz', $width); - $xmlWriter->writeAttribute('w:color', $color); + $xmlWriter->writeAttributeIf($color != null, 'w:color', $color); if (!empty($this->attributes)) { if (isset($this->attributes['space'])) { $xmlWriter->writeAttribute('w:space', $this->attributes['space']); @@ -105,10 +111,9 @@ private function writeSide(XMLWriter $xmlWriter, $side, $width, $color = null) /** * Set sizes. * - * @param integer[] $value - * @return void + * @param int[] $value */ - public function setSizes($value) + public function setSizes($value): void { $this->sizes = $value; } @@ -116,21 +121,29 @@ public function setSizes($value) /** * Set colors. * - * @param string[] $value - * @return void + * @param array $value */ - public function setColors($value) + public function setColors($value): void { $this->colors = $value; } + /** + * Set border styles. + * + * @param string[] $value + */ + public function setStyles($value): void + { + $this->styles = $value; + } + /** * Set attributes. * * @param array $value - * @return void */ - public function setAttributes($value) + public function setAttributes($value): void { $this->attributes = $value; } diff --git a/src/PhpWord/Writer/Word2007/Style/Outline.php b/src/PhpWord/Writer/Word2007/Style/Outline.php index 06064d188a..45f37cc61e 100644 --- a/src/PhpWord/Writer/Word2007/Style/Outline.php +++ b/src/PhpWord/Writer/Word2007/Style/Outline.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Outline) { @@ -37,7 +36,7 @@ public function write() } $xmlWriter = $this->getXmlWriter(); - $xmlWriter->startElement("v:stroke"); + $xmlWriter->startElement('v:stroke'); $xmlWriter->writeAttribute('on', 't'); $xmlWriter->writeAttributeIf($style->getColor() !== null, 'color', $style->getColor()); $xmlWriter->writeAttributeIf($style->getWeight() !== null, 'weight', $style->getWeight() . $style->getUnit()); diff --git a/src/PhpWord/Writer/Word2007/Style/Paragraph.php b/src/PhpWord/Writer/Word2007/Style/Paragraph.php index 039b78bfad..55f51a54d6 100644 --- a/src/PhpWord/Writer/Word2007/Style/Paragraph.php +++ b/src/PhpWord/Writer/Word2007/Style/Paragraph.php @@ -1,4 +1,5 @@ getXmlWriter(); - $isStyleName = $this->isInline && !is_null($this->style) && is_string($this->style); + $isStyleName = $this->isInline && null !== $this->style && is_string($this->style); if ($isStyleName) { if (!$this->withoutPPR) { $xmlWriter->startElement('w:pPr'); @@ -70,10 +69,8 @@ public function write() /** * Write full style. - * - * @return void */ - private function writeStyle() + private function writeStyle(): void { $style = $this->getStyle(); if (!$style instanceof ParagraphStyle) { @@ -91,17 +88,35 @@ private function writeStyle() $xmlWriter->writeElementIf($styles['name'] !== null, 'w:pStyle', 'w:val', $styles['name']); } - // Alignment - $styleWriter = new Alignment($xmlWriter, new AlignmentStyle(array('value' => $styles['alignment']))); - $styleWriter->write(); - // Pagination $xmlWriter->writeElementIf($styles['pagination']['widowControl'] === false, 'w:widowControl', 'w:val', '0'); $xmlWriter->writeElementIf($styles['pagination']['keepNext'] === true, 'w:keepNext', 'w:val', '1'); $xmlWriter->writeElementIf($styles['pagination']['keepLines'] === true, 'w:keepLines', 'w:val', '1'); $xmlWriter->writeElementIf($styles['pagination']['pageBreak'] === true, 'w:pageBreakBefore', 'w:val', '1'); - // Child style: indentation, spacing, and shading + // Paragraph alignment + if ('' !== $styles['alignment']) { + $paragraphAlignment = new ParagraphAlignment($styles['alignment']); + $xmlWriter->startElement($paragraphAlignment->getName()); + foreach ($paragraphAlignment->getAttributes() as $attributeName => $attributeValue) { + $xmlWriter->writeAttribute($attributeName, $attributeValue); + } + $xmlWriter->endElement(); + } + + //Right to left + $xmlWriter->writeElementIf($styles['bidi'] === true, 'w:bidi'); + + //Paragraph contextualSpacing + $xmlWriter->writeElementIf($styles['contextualSpacing'] === true, 'w:contextualSpacing'); + + //Paragraph textAlignment + $xmlWriter->writeElementIf($styles['textAlignment'] !== null, 'w:textAlignment', 'w:val', $styles['textAlignment']); + + // Hyphenation + $xmlWriter->writeElementIf($styles['suppressAutoHyphens'] === true, 'w:suppressAutoHyphens'); + + // Child style: alignment, indentation, spacing, and shading $this->writeChildStyle($xmlWriter, 'Indentation', $styles['indentation']); $this->writeChildStyle($xmlWriter, 'Spacing', $styles['spacing']); $this->writeChildStyle($xmlWriter, 'Shading', $styles['shading']); @@ -118,6 +133,7 @@ private function writeStyle() $styleWriter = new MarginBorder($xmlWriter); $styleWriter->setSizes($style->getBorderSize()); + $styleWriter->setStyles($style->getBorderStyle()); $styleWriter->setColors($style->getBorderColor()); $styleWriter->write(); @@ -132,14 +148,12 @@ private function writeStyle() /** * Write tabs. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Tab[] $tabs - * @return void + * @param Style\Tab[] $tabs */ - private function writeTabs(XMLWriter $xmlWriter, $tabs) + private function writeTabs(XMLWriter $xmlWriter, $tabs): void { if (!empty($tabs)) { - $xmlWriter->startElement("w:tabs"); + $xmlWriter->startElement('w:tabs'); foreach ($tabs as $tab) { $styleWriter = new Tab($xmlWriter, $tab); $styleWriter->write(); @@ -151,16 +165,14 @@ private function writeTabs(XMLWriter $xmlWriter, $tabs) /** * Write numbering. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param array $numbering - * @return void */ - private function writeNumbering(XMLWriter $xmlWriter, $numbering) + private function writeNumbering(XMLWriter $xmlWriter, $numbering): void { $numStyle = $numbering['style']; $numLevel = $numbering['level']; - /** @var \PhpOffice\PhpWord\Style\Numbering $numbering */ + /** @var Style\Numbering $numbering */ $numbering = Style::getStyle($numStyle); if ($numStyle !== null && $numbering !== null) { $xmlWriter->startElement('w:numPr'); @@ -182,9 +194,8 @@ private function writeNumbering(XMLWriter $xmlWriter, $numbering) * Set without w:pPr. * * @param bool $value - * @return void */ - public function setWithoutPPR($value) + public function setWithoutPPR($value): void { $this->withoutPPR = $value; } @@ -193,9 +204,8 @@ public function setWithoutPPR($value) * Set is inline. * * @param bool $value - * @return void */ - public function setIsInline($value) + public function setIsInline($value): void { $this->isInline = $value; } diff --git a/src/PhpWord/Writer/Word2007/Style/Row.php b/src/PhpWord/Writer/Word2007/Style/Row.php index 98841dd846..2b9d804f39 100644 --- a/src/PhpWord/Writer/Word2007/Style/Row.php +++ b/src/PhpWord/Writer/Word2007/Style/Row.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Row) { @@ -60,9 +59,8 @@ public function write() * Set height. * * @param int $value - * @return void */ - public function setHeight($value = null) + public function setHeight($value = null): void { $this->height = $value; } diff --git a/src/PhpWord/Writer/Word2007/Style/Section.php b/src/PhpWord/Writer/Word2007/Style/Section.php index 486d0ed228..cb2c9a083a 100644 --- a/src/PhpWord/Writer/Word2007/Style/Section.php +++ b/src/PhpWord/Writer/Word2007/Style/Section.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof SectionStyle) { @@ -41,7 +40,7 @@ public function write() // Break type $breakType = $style->getBreakType(); - $xmlWriter->writeElementIf(!is_null($breakType), 'w:type', 'w:val', $breakType); + $xmlWriter->writeElementIf(null !== $breakType, 'w:type', 'w:val', $breakType); // Page size & orientation $xmlWriter->startElement('w:pgSz'); @@ -50,19 +49,23 @@ public function write() $xmlWriter->writeAttribute('w:h', $style->getPageSizeH()); $xmlWriter->endElement(); // w:pgSz + // Vertical alignment + $vAlign = $style->getVAlign(); + $xmlWriter->writeElementIf(null !== $vAlign, 'w:vAlign', 'w:val', $vAlign); + // Margins - $margins = array( - 'w:top' => array('getMarginTop', SectionStyle::DEFAULT_MARGIN), - 'w:right' => array('getMarginRight', SectionStyle::DEFAULT_MARGIN), - 'w:bottom' => array('getMarginBottom', SectionStyle::DEFAULT_MARGIN), - 'w:left' => array('getMarginLeft', SectionStyle::DEFAULT_MARGIN), - 'w:header' => array('getHeaderHeight', SectionStyle::DEFAULT_HEADER_HEIGHT), - 'w:footer' => array('getFooterHeight', SectionStyle::DEFAULT_FOOTER_HEIGHT), - 'w:gutter' => array('getGutter', SectionStyle::DEFAULT_GUTTER), - ); + $margins = [ + 'w:top' => ['getMarginTop', SectionStyle::DEFAULT_MARGIN], + 'w:right' => ['getMarginRight', SectionStyle::DEFAULT_MARGIN], + 'w:bottom' => ['getMarginBottom', SectionStyle::DEFAULT_MARGIN], + 'w:left' => ['getMarginLeft', SectionStyle::DEFAULT_MARGIN], + 'w:header' => ['getHeaderHeight', SectionStyle::DEFAULT_HEADER_HEIGHT], + 'w:footer' => ['getFooterHeight', SectionStyle::DEFAULT_FOOTER_HEIGHT], + 'w:gutter' => ['getGutter', SectionStyle::DEFAULT_GUTTER], + ]; $xmlWriter->startElement('w:pgMar'); foreach ($margins as $attribute => $value) { - list($method, $default) = $value; + [$method, $default] = $value; $xmlWriter->writeAttribute($attribute, $this->convertTwip($style->$method(), $default)); } $xmlWriter->endElement(); @@ -75,7 +78,7 @@ public function write() $styleWriter = new MarginBorder($xmlWriter); $styleWriter->setSizes($style->getBorderSize()); $styleWriter->setColors($style->getBorderColor()); - $styleWriter->setAttributes(array('space' => '24')); + $styleWriter->setAttributes(['space' => '24']); $styleWriter->write(); $xmlWriter->endElement(); @@ -90,7 +93,7 @@ public function write() // Page numbering start $pageNum = $style->getPageNumberingStart(); - $xmlWriter->writeElementIf(!is_null($pageNum), 'w:pgNumType', 'w:start', $pageNum); + $xmlWriter->writeElementIf(null !== $pageNum, 'w:pgNumType', 'w:start', $pageNum); // Line numbering $styleWriter = new LineNumbering($xmlWriter, $style->getLineNumbering()); diff --git a/src/PhpWord/Writer/Word2007/Style/Shading.php b/src/PhpWord/Writer/Word2007/Style/Shading.php index 8ef8c6b184..bf7476cdb1 100644 --- a/src/PhpWord/Writer/Word2007/Style/Shading.php +++ b/src/PhpWord/Writer/Word2007/Style/Shading.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Shading) { @@ -38,9 +37,9 @@ public function write() $xmlWriter = $this->getXmlWriter(); $xmlWriter->startElement('w:shd'); - $xmlWriter->writeAttribute('w:val', $style->getPattern()); - $xmlWriter->writeAttribute('w:color', $style->getColor()); - $xmlWriter->writeAttribute('w:fill', $style->getFill()); + $xmlWriter->writeAttributeIf(null !== $style->getPattern(), 'w:val', $style->getPattern()); + $xmlWriter->writeAttributeIf(null !== $style->getColor(), 'w:color', $style->getColor()); + $xmlWriter->writeAttributeIf(null !== $style->getFill(), 'w:fill', $style->getFill()); $xmlWriter->endElement(); } } diff --git a/src/PhpWord/Writer/Word2007/Style/Shadow.php b/src/PhpWord/Writer/Word2007/Style/Shadow.php index cc74605994..33678155b0 100644 --- a/src/PhpWord/Writer/Word2007/Style/Shadow.php +++ b/src/PhpWord/Writer/Word2007/Style/Shadow.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Shadow) { @@ -37,7 +36,7 @@ public function write() } $xmlWriter = $this->getXmlWriter(); - $xmlWriter->startElement("v:shadow"); + $xmlWriter->startElement('v:shadow'); $xmlWriter->writeAttribute('on', 't'); $xmlWriter->writeAttributeIf($style->getColor() !== null, 'color', $style->getColor()); $xmlWriter->writeAttributeIf($style->getOffset() !== null, 'offset', $style->getOffset()); diff --git a/src/PhpWord/Writer/Word2007/Style/Shape.php b/src/PhpWord/Writer/Word2007/Style/Shape.php index ba8dce5bd1..06082f1e71 100644 --- a/src/PhpWord/Writer/Word2007/Style/Shape.php +++ b/src/PhpWord/Writer/Word2007/Style/Shape.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Shape) { @@ -38,7 +37,7 @@ public function write() $xmlWriter = $this->getXmlWriter(); - $childStyles = array('Frame', 'Fill', 'Outline', 'Shadow', 'Extrusion'); + $childStyles = ['Frame', 'Fill', 'Outline', 'Shadow', 'Extrusion']; foreach ($childStyles as $childStyle) { $method = "get{$childStyle}"; $this->writeChildStyle($xmlWriter, $childStyle, $style->$method()); diff --git a/src/PhpWord/Writer/Word2007/Style/Spacing.php b/src/PhpWord/Writer/Word2007/Style/Spacing.php index 2f7e122a22..6b99f5ac4b 100644 --- a/src/PhpWord/Writer/Word2007/Style/Spacing.php +++ b/src/PhpWord/Writer/Word2007/Style/Spacing.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Spacing) { @@ -40,15 +39,19 @@ public function write() $xmlWriter->startElement('w:spacing'); $before = $style->getBefore(); - $xmlWriter->writeAttributeIf(!is_null($before), 'w:before', $this->convertTwip($before)); + $xmlWriter->writeAttributeIf(null !== $before, 'w:before', $this->convertTwip($before)); $after = $style->getAfter(); - $xmlWriter->writeAttributeIf(!is_null($after), 'w:after', $this->convertTwip($after)); + $xmlWriter->writeAttributeIf(null !== $after, 'w:after', $this->convertTwip($after)); $line = $style->getLine(); - $xmlWriter->writeAttributeIf(!is_null($line), 'w:line', $line); + //if linerule is auto, the spacing is supposed to include the height of the line itself, which is 240 twips + if (null !== $line && 'auto' === $style->getLineRule()) { + $line += \PhpOffice\PhpWord\Style\Paragraph::LINE_HEIGHT; + } + $xmlWriter->writeAttributeIf(null !== $line, 'w:line', $line); - $xmlWriter->writeAttributeIf(!is_null($line), 'w:lineRule', $style->getRule()); + $xmlWriter->writeAttributeIf(null !== $line, 'w:lineRule', $style->getLineRule()); $xmlWriter->endElement(); } diff --git a/src/PhpWord/Writer/Word2007/Style/Tab.php b/src/PhpWord/Writer/Word2007/Style/Tab.php index aa00acc788..4a8da4045c 100644 --- a/src/PhpWord/Writer/Word2007/Style/Tab.php +++ b/src/PhpWord/Writer/Word2007/Style/Tab.php @@ -1,4 +1,5 @@ getStyle(); if (!$style instanceof \PhpOffice\PhpWord\Style\Tab) { @@ -37,9 +36,9 @@ public function write() } $xmlWriter = $this->getXmlWriter(); - $xmlWriter->startElement("w:tab"); - $xmlWriter->writeAttribute("w:val", $style->getType()); - $xmlWriter->writeAttribute("w:leader", $style->getLeader()); + $xmlWriter->startElement('w:tab'); + $xmlWriter->writeAttribute('w:val', $style->getType()); + $xmlWriter->writeAttribute('w:leader', $style->getLeader()); $xmlWriter->writeAttribute('w:pos', $this->convertTwip($style->getPosition())); $xmlWriter->endElement(); } diff --git a/src/PhpWord/Writer/Word2007/Style/Table.php b/src/PhpWord/Writer/Word2007/Style/Table.php index 8bbad107c3..711f3ecde7 100644 --- a/src/PhpWord/Writer/Word2007/Style/Table.php +++ b/src/PhpWord/Writer/Word2007/Style/Table.php @@ -1,4 +1,5 @@ getStyle(); $xmlWriter = $this->getXmlWriter(); @@ -50,8 +50,8 @@ public function write() $xmlWriter->startElement('w:tblStyle'); $xmlWriter->writeAttribute('w:val', $style); $xmlWriter->endElement(); - if ($this->width !== null) { - $this->writeWidth($xmlWriter, $this->width, 'pct'); + if (null !== $this->width) { + $this->writeTblWidth($xmlWriter, 'w:tblW', TblWidth::PERCENT, $this->width); } $xmlWriter->endElement(); } @@ -59,21 +59,34 @@ public function write() /** * Write full style. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Table $style - * @return void */ - private function writeStyle(XMLWriter $xmlWriter, TableStyle $style) + private function writeStyle(XMLWriter $xmlWriter, TableStyle $style): void { // w:tblPr $xmlWriter->startElement('w:tblPr'); - // Alignment - $styleWriter = new Alignment($xmlWriter, new AlignmentStyle(array('value' => $style->getAlign()))); + // Table alignment + if ('' !== $style->getAlignment()) { + $tableAlignment = new TableAlignment($style->getAlignment()); + $xmlWriter->startElement($tableAlignment->getName()); + foreach ($tableAlignment->getAttributes() as $attributeName => $attributeValue) { + $xmlWriter->writeAttribute($attributeName, $attributeValue); + } + $xmlWriter->endElement(); + } + + $this->writeTblWidth($xmlWriter, 'w:tblW', $style->getUnit(), $style->getWidth()); + $this->writeTblWidth($xmlWriter, 'w:tblCellSpacing', TblWidth::TWIP, $style->getCellSpacing()); + $this->writeIndent($xmlWriter, $style); + $this->writeLayout($xmlWriter, $style->getLayout()); + + // Position + $styleWriter = new TablePosition($xmlWriter, $style->getPosition()); $styleWriter->write(); - $this->writeWidth($xmlWriter, $style->getWidth(), $style->getUnit()); + //Right to left + $xmlWriter->writeElementIf($style->isBidiVisual() !== null, 'w:bidiVisual', 'w:val', $this->writeOnOf($style->isBidiVisual())); + $this->writeMargin($xmlWriter, $style); $this->writeBorder($xmlWriter, $style); @@ -89,29 +102,21 @@ private function writeStyle(XMLWriter $xmlWriter, TableStyle $style) } /** - * Write width. + * Enable/Disable automatic resizing of the table. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param int $width - * @param string $unit - * @return void + * @param string $layout autofit / fixed */ - private function writeWidth(XMLWriter $xmlWriter, $width, $unit) + private function writeLayout(XMLWriter $xmlWriter, $layout): void { - $xmlWriter->startElement('w:tblW'); - $xmlWriter->writeAttribute('w:w', $width); - $xmlWriter->writeAttribute('w:type', $unit); - $xmlWriter->endElement(); // w:tblW + $xmlWriter->startElement('w:tblLayout'); + $xmlWriter->writeAttribute('w:type', $layout); + $xmlWriter->endElement(); // w:tblLayout } /** * Write margin. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Table $style - * @return void */ - private function writeMargin(XMLWriter $xmlWriter, TableStyle $style) + private function writeMargin(XMLWriter $xmlWriter, TableStyle $style): void { if ($style->hasMargin()) { $xmlWriter->startElement('w:tblCellMar'); @@ -126,12 +131,8 @@ private function writeMargin(XMLWriter $xmlWriter, TableStyle $style) /** * Write border. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Table $style - * @return void */ - private function writeBorder(XMLWriter $xmlWriter, TableStyle $style) + private function writeBorder(XMLWriter $xmlWriter, TableStyle $style): void { if ($style->hasBorder()) { $xmlWriter->startElement('w:tblBorders'); @@ -146,13 +147,27 @@ private function writeBorder(XMLWriter $xmlWriter, TableStyle $style) } /** - * Write row style. + * Writes a table width. * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Table $style - * @return void + * @param string $elementName + * @param string $unit + * @param null|float|int $width */ - private function writeFirstRow(XMLWriter $xmlWriter, TableStyle $style) + private function writeTblWidth(XMLWriter $xmlWriter, $elementName, $unit, $width = null): void + { + if (null === $width) { + return; + } + $xmlWriter->startElement($elementName); + $xmlWriter->writeAttribute('w:w', $width); + $xmlWriter->writeAttribute('w:type', $unit); + $xmlWriter->endElement(); + } + + /** + * Write row style. + */ + private function writeFirstRow(XMLWriter $xmlWriter, TableStyle $style): void { $xmlWriter->startElement('w:tblStylePr'); $xmlWriter->writeAttribute('w:type', 'firstRow'); @@ -167,14 +182,10 @@ private function writeFirstRow(XMLWriter $xmlWriter, TableStyle $style) /** * Write shading. - * - * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param \PhpOffice\PhpWord\Style\Table $style - * @return void */ - private function writeShading(XMLWriter $xmlWriter, TableStyle $style) + private function writeShading(XMLWriter $xmlWriter, TableStyle $style): void { - if ($style->getShading() !== null) { + if (null !== $style->getShading()) { $xmlWriter->startElement('w:tcPr'); $styleWriter = new Shading($xmlWriter, $style->getShading()); @@ -188,10 +199,20 @@ private function writeShading(XMLWriter $xmlWriter, TableStyle $style) * Set width. * * @param int $value - * @return void */ - public function setWidth($value = null) + public function setWidth($value = null): void { $this->width = $value; } + + private function writeIndent(XMLWriter $xmlWriter, TableStyle $style): void + { + $indent = $style->getIndent(); + + if ($indent === null) { + return; + } + + $this->writeTblWidth($xmlWriter, 'w:tblInd', $indent->getType(), $indent->getValue()); + } } diff --git a/src/PhpWord/Writer/Word2007/Style/TablePosition.php b/src/PhpWord/Writer/Word2007/Style/TablePosition.php new file mode 100644 index 0000000000..71668032e5 --- /dev/null +++ b/src/PhpWord/Writer/Word2007/Style/TablePosition.php @@ -0,0 +1,66 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\TablePosition) { + return; + } + + $values = []; + $properties = [ + 'leftFromText', + 'rightFromText', + 'topFromText', + 'bottomFromText', + 'vertAnchor', + 'horzAnchor', + 'tblpXSpec', + 'tblpX', + 'tblpYSpec', + 'tblpY', + ]; + foreach ($properties as $property) { + $method = 'get' . $property; + if (method_exists($style, $method)) { + $values[$property] = $style->$method(); + } + } + $values = array_filter($values); + + if ($values) { + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:tblpPr'); + foreach ($values as $property => $value) { + $xmlWriter->writeAttribute('w:' . $property, $value); + } + $xmlWriter->endElement(); + } + } +} diff --git a/src/PhpWord/Writer/Word2007/Style/TextBox.php b/src/PhpWord/Writer/Word2007/Style/TextBox.php index 20c68c7421..2f8b5e9b3d 100644 --- a/src/PhpWord/Writer/Word2007/Style/TextBox.php +++ b/src/PhpWord/Writer/Word2007/Style/TextBox.php @@ -1,17 +1,16 @@ getStyle(); if (!$style instanceof TextBoxStyle || !$style->hasInnerMargins()) { @@ -46,10 +43,8 @@ public function writeInnerMargin() /** * Writer border. - * - * @return void */ - public function writeBorder() + public function writeBorder(): void { $style = $this->getStyle(); if (!$style instanceof TextBoxStyle) { diff --git a/src/PhpWord/Writer/WriterInterface.php b/src/PhpWord/Writer/WriterInterface.php index eda99f2788..86743e7bd2 100644 --- a/src/PhpWord/Writer/WriterInterface.php +++ b/src/PhpWord/Writer/WriterInterface.php @@ -1,4 +1,5 @@ assertContains( - array('PhpOffice\\PhpWord\\Autoloader', 'autoload'), - spl_autoload_functions() - ); - } - - /** - * Autoload - */ - public function testAutoload() - { - $declaredCount = count(get_declared_classes()); - Autoloader::autoload('Foo'); - $this->assertCount( - $declaredCount, - get_declared_classes(), - 'PhpOffice\\PhpWord\\Autoloader::autoload() is trying to load ' . - 'classes outside of the PhpOffice\\PhpWord namespace' - ); - // TODO change this class to the main PhpWord class when it is namespaced - Autoloader::autoload('PhpOffice\\PhpWord\\Exception\\InvalidStyleException'); - $this->assertTrue( - in_array('PhpOffice\\PhpWord\\Exception\\InvalidStyleException', get_declared_classes()), - 'PhpOffice\\PhpWord\\Autoloader::autoload() failed to autoload the ' . - 'PhpOffice\\PhpWord\\Exception\\InvalidStyleException class' - ); - } -} diff --git a/tests/PhpWord/Tests/Collection/CollectionTest.php b/tests/PhpWord/Tests/Collection/CollectionTest.php deleted file mode 100644 index 833b3e8072..0000000000 --- a/tests/PhpWord/Tests/Collection/CollectionTest.php +++ /dev/null @@ -1,39 +0,0 @@ -addItem(new Footnote()); // addItem #1 - - $this->assertEquals(2, $object->addItem(new Footnote())); // addItem #2. Should returns new item index - $this->assertCount(2, $object->getItems()); // getItems returns array - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Footnote', $object->getItem(1)); // getItem returns object - $this->assertNull($object->getItem(3)); // getItem returns null when invalid index is referenced - - $object->setItem(2, null); // Set item #2 to null - - $this->assertNull($object->getItem(2)); // Check if it's null - } -} diff --git a/tests/PhpWord/Tests/Element/AbstractElementTest.php b/tests/PhpWord/Tests/Element/AbstractElementTest.php deleted file mode 100644 index 180dd4d0b6..0000000000 --- a/tests/PhpWord/Tests/Element/AbstractElementTest.php +++ /dev/null @@ -1,45 +0,0 @@ -getMockForAbstractClass('\PhpOffice\PhpWord\Element\AbstractElement'); - $ival = rand(0, 100); - $stub->setElementIndex($ival); - $this->assertEquals($stub->getElementIndex(), $ival); - } - - /** - * Test set/get element unique Id - */ - public function testElementId() - { - $stub = $this->getMockForAbstractClass('\PhpOffice\PhpWord\Element\AbstractElement'); - $stub->setElementId(); - $this->assertEquals(strlen($stub->getElementId()), 6); - } -} diff --git a/tests/PhpWord/Tests/Element/CellTest.php b/tests/PhpWord/Tests/Element/CellTest.php deleted file mode 100644 index aa07ec0394..0000000000 --- a/tests/PhpWord/Tests/Element/CellTest.php +++ /dev/null @@ -1,275 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Cell', $oCell); - $this->assertEquals($oCell->getWidth(), null); - } - - /** - * New instance with array - */ - public function testConstructWithStyleArray() - { - $oCell = new Cell(null, array('valign' => 'center')); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Cell', $oCell->getStyle()); - $this->assertEquals($oCell->getWidth(), null); - } - - /** - * Add text - */ - public function testAddText() - { - $oCell = new Cell(); - $element = $oCell->addText('text'); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); - } - - /** - * Add non-UTF8 - */ - public function testAddTextNotUTF8() - { - $oCell = new Cell(); - $element = $oCell->addText(utf8_decode('ééé')); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); - $this->assertEquals($element->getText(), 'ééé'); - } - - /** - * Add link - */ - public function testAddLink() - { - $oCell = new Cell(); - $element = $oCell->addLink(utf8_decode('ééé'), utf8_decode('ééé')); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); - } - - /** - * Add text break - */ - public function testAddTextBreak() - { - $oCell = new Cell(); - $oCell->addTextBreak(); - - $this->assertCount(1, $oCell->getElements()); - } - - /** - * Add list item - */ - public function testAddListItem() - { - $oCell = new Cell(); - $element = $oCell->addListItem('text'); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\ListItem', $element); - $this->assertEquals($element->getTextObject()->getText(), 'text'); - } - - /** - * Add list item non-UTF8 - */ - public function testAddListItemNotUTF8() - { - $oCell = new Cell(); - $element = $oCell->addListItem(utf8_decode('ééé')); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\ListItem', $element); - $this->assertEquals($element->getTextObject()->getText(), 'ééé'); - } - - /** - * Add image section - */ - public function testAddImageSection() - { - $src = __DIR__ . "/../_files/images/earth.jpg"; - $oCell = new Cell(); - $element = $oCell->addImage($src); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); - } - - /** - * Add image header - */ - public function testAddImageHeader() - { - $src = __DIR__ . "/../_files/images/earth.jpg"; - $oCell = new Cell('header', 1); - $element = $oCell->addImage($src); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); - } - - /** - * Add image footer - */ - public function testAddImageFooter() - { - $src = __DIR__ . "/../_files/images/earth.jpg"; - $oCell = new Cell('footer', 1); - $element = $oCell->addImage($src); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); - } - - /** - * Add image section by URL - */ - public function testAddImageSectionByUrl() - { - $oCell = new Cell(); - $element = $oCell->addImage( - 'http://php.net/images/logos/php-med-trans-light.gif' - ); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); - } - - /** - * Add object - */ - public function testAddObjectXLS() - { - $src = __DIR__ . "/../_files/documents/sheet.xls"; - $oCell = new Cell(); - $element = $oCell->addObject($src); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Object', $element); - } - - /** - * Test add object exception - * - * @expectedException \PhpOffice\PhpWord\Exception\InvalidObjectException - */ - public function testAddObjectException() - { - $src = __DIR__ . "/../_files/xsl/passthrough.xsl"; - $oCell = new Cell(); - $oCell->addObject($src); - } - - /** - * Add preserve text - */ - public function testAddPreserveText() - { - $oCell = new Cell(); - $oCell->setDocPart('Header', 1); - $element = $oCell->addPreserveText('text'); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); - } - - /** - * Add preserve text non-UTF8 - */ - public function testAddPreserveTextNotUTF8() - { - $oCell = new Cell(); - $oCell->setDocPart('Header', 1); - $element = $oCell->addPreserveText(utf8_decode('ééé')); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); - $this->assertEquals($element->getText(), array('ééé')); - } - - /** - * Add preserve text exception - * - * @expectedException \BadMethodCallException - */ - public function testAddPreserveTextException() - { - $oCell = new Cell(); - $oCell->setDocPart('Section', 1); - $oCell->addPreserveText('text'); - } - - /** - * Add text run - */ - public function testCreateTextRun() - { - $oCell = new Cell(); - $element = $oCell->addTextRun(); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $element); - } - - /** - * Add check box - */ - public function testAddCheckBox() - { - $oCell = new Cell(); - $element = $oCell->addCheckBox(utf8_decode('ééé'), utf8_decode('ééé')); - - $this->assertCount(1, $oCell->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\CheckBox', $element); - } - - /** - * Get elements - */ - public function testGetElements() - { - $oCell = new Cell(); - - $this->assertInternalType('array', $oCell->getElements()); - } -} diff --git a/tests/PhpWord/Tests/Element/CheckBoxTest.php b/tests/PhpWord/Tests/Element/CheckBoxTest.php deleted file mode 100644 index 8f7bf3cd0f..0000000000 --- a/tests/PhpWord/Tests/Element/CheckBoxTest.php +++ /dev/null @@ -1,87 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\CheckBox', $oCheckBox); - $this->assertEquals(null, $oCheckBox->getText()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oCheckBox->getFontStyle()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oCheckBox->getParagraphStyle()); - } - - /** - * Get name and text - */ - public function testCheckBox() - { - $oCheckBox = new CheckBox('chkBox', 'CheckBox'); - - $this->assertEquals($oCheckBox->getName(), 'chkBox'); - $this->assertEquals($oCheckBox->getText(), 'CheckBox'); - } - - /** - * Get font style - */ - public function testFont() - { - $oCheckBox = new CheckBox('chkBox', 'CheckBox', 'fontStyle'); - $this->assertEquals($oCheckBox->getFontStyle(), 'fontStyle'); - - $oCheckBox->setFontStyle(array('bold' => true, 'italic' => true, 'size' => 16)); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oCheckBox->getFontStyle()); - } - - /** - * Font style as object - */ - public function testFontObject() - { - $font = new Font(); - $oCheckBox = new CheckBox('chkBox', 'CheckBox', $font); - $this->assertEquals($oCheckBox->getFontStyle(), $font); - } - - /** - * Get paragraph style - */ - public function testParagraph() - { - $oCheckBox = new CheckBox('chkBox', 'CheckBox', 'fontStyle', 'paragraphStyle'); - $this->assertEquals($oCheckBox->getParagraphStyle(), 'paragraphStyle'); - - $oCheckBox->setParagraphStyle(array('align' => 'center', 'spaceAfter' => 100)); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oCheckBox->getParagraphStyle()); - } -} diff --git a/tests/PhpWord/Tests/Element/FieldTest.php b/tests/PhpWord/Tests/Element/FieldTest.php deleted file mode 100644 index 2f9193d461..0000000000 --- a/tests/PhpWord/Tests/Element/FieldTest.php +++ /dev/null @@ -1,111 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Field', $oField); - } - - /** - * New instance with type - */ - public function testConstructWithType() - { - $oField = new Field('DATE'); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Field', $oField); - $this->assertEquals($oField->getType(), 'DATE'); - } - - /** - * New instance with type and properties - */ - public function testConstructWithTypeProperties() - { - $oField = new Field('DATE', array('dateformat'=>'d-M-yyyy')); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Field', $oField); - $this->assertEquals($oField->getType(), 'DATE'); - $this->assertEquals($oField->getProperties(), array('dateformat'=>'d-M-yyyy')); - } - - /** - * New instance with type and properties and options - */ - public function testConstructWithTypePropertiesOptions() - { - $oField = new Field('DATE', array('dateformat'=>'d-M-yyyy'), array('SakaEraCalendar', 'PreserveFormat')); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Field', $oField); - $this->assertEquals($oField->getType(), 'DATE'); - $this->assertEquals($oField->getProperties(), array('dateformat'=>'d-M-yyyy')); - $this->assertEquals($oField->getOptions(), array('SakaEraCalendar', 'PreserveFormat')); - } - - /** - * Test setType exception - * - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Invalid type - */ - public function testSetTypeException() - { - $object = new Field(); - $object->setType('foo'); - } - - /** - * Test setProperties exception - * - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Invalid property - */ - public function testSetPropertiesException() - { - $object = new Field('PAGE'); - $object->setProperties(array('foo' => 'bar')); - } - - /** - * Test setOptions exception - * - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Invalid option - */ - public function testSetOptionsException() - { - $object = new Field('PAGE'); - $object->setOptions(array('foo' => 'bar')); - } -} diff --git a/tests/PhpWord/Tests/Element/FooterTest.php b/tests/PhpWord/Tests/Element/FooterTest.php deleted file mode 100644 index c5d04b410a..0000000000 --- a/tests/PhpWord/Tests/Element/FooterTest.php +++ /dev/null @@ -1,177 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Footer', $oFooter); - $this->assertEquals($oFooter->getSectionId(), $iVal); - } - - /** - * Add text - */ - public function testAddText() - { - $oFooter = new Footer(1); - $element = $oFooter->addText('text'); - - $this->assertCount(1, $oFooter->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); - } - - /** - * Add text non-UTF8 - */ - public function testAddTextNotUTF8() - { - $oFooter = new Footer(1); - $element = $oFooter->addText(utf8_decode('ééé')); - - $this->assertCount(1, $oFooter->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); - $this->assertEquals($element->getText(), 'ééé'); - } - - /** - * Add text break - */ - public function testAddTextBreak() - { - $oFooter = new Footer(1); - $iVal = rand(1, 1000); - $oFooter->addTextBreak($iVal); - - $this->assertCount($iVal, $oFooter->getElements()); - } - - /** - * Add text run - */ - public function testCreateTextRun() - { - $oFooter = new Footer(1); - $element = $oFooter->addTextRun(); - - $this->assertCount(1, $oFooter->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $element); - } - - /** - * Add table - */ - public function testAddTable() - { - $oFooter = new Footer(1); - $element = $oFooter->addTable(); - - $this->assertCount(1, $oFooter->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Table', $element); - } - - /** - * Add image - */ - public function testAddImage() - { - $src = __DIR__ . "/../_files/images/earth.jpg"; - $oFooter = new Footer(1); - $element = $oFooter->addImage($src); - - $this->assertCount(1, $oFooter->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); - } - - /** - * Add image by URL - */ - public function testAddImageByUrl() - { - $oFooter = new Footer(1); - $element = $oFooter->addImage( - 'http://php.net/images/logos/php-med-trans-light.gif' - ); - - $this->assertCount(1, $oFooter->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); - } - - /** - * Add preserve text - */ - public function testAddPreserveText() - { - $oFooter = new Footer(1); - $element = $oFooter->addPreserveText('text'); - - $this->assertCount(1, $oFooter->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); - } - - /** - * Add preserve text non-UTF8 - */ - public function testAddPreserveTextNotUTF8() - { - $oFooter = new Footer(1); - $element = $oFooter->addPreserveText(utf8_decode('ééé')); - - $this->assertCount(1, $oFooter->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); - $this->assertEquals($element->getText(), array('ééé')); - } - - /** - * Get elements - */ - public function testGetElements() - { - $oFooter = new Footer(1); - - $this->assertInternalType('array', $oFooter->getElements()); - } - - /** - * Set/get relation Id - */ - public function testRelationID() - { - $oFooter = new Footer(0); - - $iVal = rand(1, 1000); - $oFooter->setRelationId($iVal); - - $this->assertEquals($oFooter->getRelationId(), $iVal); - $this->assertEquals(Footer::AUTO, $oFooter->getType()); - } -} diff --git a/tests/PhpWord/Tests/Element/FootnoteTest.php b/tests/PhpWord/Tests/Element/FootnoteTest.php deleted file mode 100644 index 2ba015d741..0000000000 --- a/tests/PhpWord/Tests/Element/FootnoteTest.php +++ /dev/null @@ -1,119 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Footnote', $oFootnote); - $this->assertCount(0, $oFootnote->getElements()); - $this->assertEquals($oFootnote->getParagraphStyle(), null); - } - - /** - * New instance with string parameter - */ - public function testConstructString() - { - $oFootnote = new Footnote('pStyle'); - - $this->assertEquals($oFootnote->getParagraphStyle(), 'pStyle'); - } - - /** - * New instance with array parameter - */ - public function testConstructArray() - { - $oFootnote = new Footnote(array('spacing' => 100)); - - $this->assertInstanceOf( - 'PhpOffice\\PhpWord\\Style\\Paragraph', - $oFootnote->getParagraphStyle() - ); - } - - /** - * Add text element - */ - public function testAddText() - { - $oFootnote = new Footnote(); - $element = $oFootnote->addText('text'); - - $this->assertCount(1, $oFootnote->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); - } - - /** - * Add text break element - */ - public function testAddTextBreak() - { - $oFootnote = new Footnote(); - $oFootnote->addTextBreak(2); - - $this->assertCount(2, $oFootnote->getElements()); - } - - /** - * Add link element - */ - public function testAddLink() - { - $oFootnote = new Footnote(); - $element = $oFootnote->addLink('http://www.google.fr/'); - - $this->assertCount(1, $oFootnote->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); - } - - /** - * Set/get reference Id - */ - public function testReferenceId() - { - $oFootnote = new Footnote(); - - $iVal = rand(1, 1000); - $oFootnote->setRelationId($iVal); - $this->assertEquals($oFootnote->getRelationId(), $iVal); - } - - /** - * Get elements - */ - public function testGetElements() - { - $oFootnote = new Footnote(); - $this->assertInternalType('array', $oFootnote->getElements()); - } -} diff --git a/tests/PhpWord/Tests/Element/HeaderTest.php b/tests/PhpWord/Tests/Element/HeaderTest.php deleted file mode 100644 index 796b24f012..0000000000 --- a/tests/PhpWord/Tests/Element/HeaderTest.php +++ /dev/null @@ -1,254 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Header', $oHeader); - $this->assertEquals($oHeader->getSectionId(), $iVal); - $this->assertEquals($oHeader->getType(), Header::AUTO); - } - - /** - * Add text - */ - public function testAddText() - { - $oHeader = new Header(1); - $element = $oHeader->addText('text'); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); - $this->assertCount(1, $oHeader->getElements()); - $this->assertEquals($element->getText(), 'text'); - } - - /** - * Add text non-UTF8 - */ - public function testAddTextNotUTF8() - { - $oHeader = new Header(1); - $element = $oHeader->addText(utf8_decode('ééé')); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); - $this->assertCount(1, $oHeader->getElements()); - $this->assertEquals($element->getText(), 'ééé'); - } - - /** - * Add text break - */ - public function testAddTextBreak() - { - $oHeader = new Header(1); - $oHeader->addTextBreak(); - $this->assertCount(1, $oHeader->getElements()); - } - - /** - * Add text break with params - */ - public function testAddTextBreakWithParams() - { - $oHeader = new Header(1); - $iVal = rand(1, 1000); - $oHeader->addTextBreak($iVal); - $this->assertCount($iVal, $oHeader->getElements()); - } - - /** - * Add text run - */ - public function testCreateTextRun() - { - $oHeader = new Header(1); - $element = $oHeader->addTextRun(); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $element); - $this->assertCount(1, $oHeader->getElements()); - } - - /** - * Add table - */ - public function testAddTable() - { - $oHeader = new Header(1); - $element = $oHeader->addTable(); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Table', $element); - $this->assertCount(1, $oHeader->getElements()); - } - - /** - * Add image - */ - public function testAddImage() - { - $src = __DIR__ . "/../_files/images/earth.jpg"; - $oHeader = new Header(1); - $element = $oHeader->addImage($src); - - $this->assertCount(1, $oHeader->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); - } - - /** - * Add image by URL - */ - public function testAddImageByUrl() - { - $oHeader = new Header(1); - $element = $oHeader->addImage( - 'http://php.net/images/logos/php-med-trans-light.gif' - ); - - $this->assertCount(1, $oHeader->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); - } - - /** - * Add preserve text - */ - public function testAddPreserveText() - { - $oHeader = new Header(1); - $element = $oHeader->addPreserveText('text'); - - $this->assertCount(1, $oHeader->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); - } - - /** - * Add preserve text non-UTF8 - */ - public function testAddPreserveTextNotUTF8() - { - $oHeader = new Header(1); - $element = $oHeader->addPreserveText(utf8_decode('ééé')); - - $this->assertCount(1, $oHeader->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); - $this->assertEquals($element->getText(), array('ééé')); - } - - /** - * Add watermark - */ - public function testAddWatermark() - { - $src = __DIR__ . "/../_files/images/earth.jpg"; - $oHeader = new Header(1); - $element = $oHeader->addWatermark($src); - - $this->assertCount(1, $oHeader->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); - } - - /** - * Get elements - */ - public function testGetElements() - { - $oHeader = new Header(1); - - $this->assertInternalType('array', $oHeader->getElements()); - } - - /** - * Set/get relation Id - */ - public function testRelationId() - { - $oHeader = new Header(1); - - $iVal = rand(1, 1000); - $oHeader->setRelationId($iVal); - $this->assertEquals($oHeader->getRelationId(), $iVal); - } - - /** - * Reset type - */ - public function testResetType() - { - $oHeader = new Header(1); - $oHeader->firstPage(); - $oHeader->resetType(); - - $this->assertEquals($oHeader->getType(), Header::AUTO); - } - - /** - * First page - */ - public function testFirstPage() - { - $oHeader = new Header(1); - $oHeader->firstPage(); - - $this->assertEquals($oHeader->getType(), Header::FIRST); - } - - /** - * Even page - */ - public function testEvenPage() - { - $oHeader = new Header(1); - $oHeader->evenPage(); - - $this->assertEquals($oHeader->getType(), Header::EVEN); - } - - /** - * Add footnote exception - * - * @expectedException BadMethodCallException - */ - public function testAddFootnoteException() - { - $header = new Header(1); - $header->addFootnote(); - } - - /** - * Set/get type - */ - public function testSetGetType() - { - $object = new Header(1); - $this->assertEquals(Header::AUTO, $object->getType()); - - $object->setType('ODD'); - $this->assertEquals(Header::AUTO, $object->getType()); - } -} diff --git a/tests/PhpWord/Tests/Element/ImageTest.php b/tests/PhpWord/Tests/Element/ImageTest.php deleted file mode 100644 index 11b33d8785..0000000000 --- a/tests/PhpWord/Tests/Element/ImageTest.php +++ /dev/null @@ -1,154 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $oImage); - $this->assertEquals($oImage->getSource(), $src); - $this->assertEquals($oImage->getMediaId(), md5($src)); - $this->assertEquals($oImage->isWatermark(), false); - $this->assertEquals($oImage->getSourceType(), Image::SOURCE_LOCAL); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Image', $oImage->getStyle()); - } - - /** - * New instance with style - */ - public function testConstructWithStyle() - { - $src = __DIR__ . "/../_files/images/firefox.png"; - $oImage = new Image( - $src, - array('width' => 210, 'height' => 210, 'align' => 'center', - 'wrappingStyle' => \PhpOffice\PhpWord\Style\Image::WRAPPING_STYLE_BEHIND) - ); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Image', $oImage->getStyle()); - } - - /** - * Valid image types - */ - public function testImages() - { - $images = array( - array('mars.jpg', 'image/jpeg', 'jpg', 'imagecreatefromjpeg', 'imagejpeg'), - array('mario.gif', 'image/gif', 'gif', 'imagecreatefromgif', 'imagegif'), - array('firefox.png', 'image/png', 'png', 'imagecreatefrompng', 'imagepng'), - array('duke_nukem.bmp', 'image/bmp', 'bmp', null, null), - array('angela_merkel.tif', 'image/tiff', 'tif', null, null), - ); - - foreach ($images as $imageData) { - list($source, $type, $extension, $createFunction, $imageFunction) = $imageData; - $source = __DIR__ . "/../_files/images/" . $source; - $image = new Image($source); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $image); - $this->assertEquals($image->getSource(), $source); - $this->assertEquals($image->getMediaId(), md5($source)); - $this->assertEquals($image->getImageType(), $type); - $this->assertEquals($image->getImageExtension(), $extension); - $this->assertEquals($image->getImageCreateFunction(), $createFunction); - $this->assertEquals($image->getImageFunction(), $imageFunction); - $this->assertFalse($image->isMemImage()); - } - } - - /** - * Get style - */ - public function testStyle() - { - $oImage = new Image( - __DIR__ . "/../_files/images/earth.jpg", - array('height' => 210, 'align' => 'center') - ); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Image', $oImage->getStyle()); - } - - /** - * Test invalid local image - * - * @expectedException \PhpOffice\PhpWord\Exception\InvalidImageException - */ - public function testInvalidImageLocal() - { - new Image(__DIR__ . "/../_files/images/thisisnotarealimage"); - } - - /** - * Test invalid PHP Image - * - * @expectedException \PhpOffice\PhpWord\Exception\InvalidImageException - */ - public function testInvalidImagePhp() - { - $object = new Image('test.php'); - $object->getSource(); - } - - /** - * Test unsupported image - * - * @expectedException \PhpOffice\PhpWord\Exception\UnsupportedImageTypeException - */ - public function testUnsupportedImage() - { - $object = new Image('http://samples.libav.org/image-samples/RACECAR.BMP'); - $object->getSource(); - } - - /** - * Get relation Id - */ - public function testRelationID() - { - $oImage = new Image(__DIR__ . "/../_files/images/earth.jpg", array('width' => 100)); - $iVal = rand(1, 1000); - $oImage->setRelationId($iVal); - $this->assertEquals($oImage->getRelationId(), $iVal); - } - - /** - * Test archived image - */ - public function testArchivedImage() - { - $archiveFile = __DIR__ . "/../_files/documents/reader.docx"; - $imageFile = 'word/media/image1.jpeg'; - $image = new Image("zip://{$archiveFile}#{$imageFile}"); - $this->assertEquals('image/jpeg', $image->getImageType()); - } -} diff --git a/tests/PhpWord/Tests/Element/LinkTest.php b/tests/PhpWord/Tests/Element/LinkTest.php deleted file mode 100644 index ada6a36a9f..0000000000 --- a/tests/PhpWord/Tests/Element/LinkTest.php +++ /dev/null @@ -1,86 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $oLink); - $this->assertEquals($oLink->getSource(), 'http://www.google.com/'); - $this->assertEquals($oLink->getText(), $oLink->getSource()); - $this->assertEquals($oLink->getFontStyle(), null); - $this->assertEquals($oLink->getParagraphStyle(), null); - } - - /** - * Create new instance with array - */ - public function testConstructWithParamsArray() - { - $oLink = new Link( - 'http://www.google.com/', - 'Search Engine', - array('color' => '0000FF', 'underline' => Font::UNDERLINE_SINGLE), - array('marginLeft' => 600, 'marginRight' => 600, 'marginTop' => 600, 'marginBottom' => 600) - ); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $oLink); - $this->assertEquals($oLink->getSource(), 'http://www.google.com/'); - $this->assertEquals($oLink->getText(), 'Search Engine'); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oLink->getFontStyle()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oLink->getParagraphStyle()); - } - - /** - * Create new instance with style name string - */ - public function testConstructWithParamsString() - { - $oLink = new Link('http://www.google.com/', null, 'fontStyle', 'paragraphStyle'); - - $this->assertEquals($oLink->getFontStyle(), 'fontStyle'); - $this->assertEquals($oLink->getParagraphStyle(), 'paragraphStyle'); - } - - /** - * Set/get relation Id - */ - public function testRelationId() - { - $oLink = new Link('http://www.google.com/'); - - $iVal = rand(1, 1000); - $oLink->setRelationId($iVal); - $this->assertEquals($oLink->getRelationId(), $iVal); - } -} diff --git a/tests/PhpWord/Tests/Element/ListItemRunTest.php b/tests/PhpWord/Tests/Element/ListItemRunTest.php deleted file mode 100644 index 7bb0c25d71..0000000000 --- a/tests/PhpWord/Tests/Element/ListItemRunTest.php +++ /dev/null @@ -1,175 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\ListItemRun', $oListItemRun); - $this->assertCount(0, $oListItemRun->getElements()); - $this->assertEquals($oListItemRun->getParagraphStyle(), null); - } - - /** - * New instance with string - */ - public function testConstructString() - { - $oListItemRun = new ListItemRun(0, null, 'pStyle'); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\ListItemRun', $oListItemRun); - $this->assertCount(0, $oListItemRun->getElements()); - $this->assertEquals($oListItemRun->getParagraphStyle(), 'pStyle'); - } - - /** - * New instance with string - */ - public function testConstructListString() - { - $oListItemRun = new ListItemRun(0, 'numberingStyle'); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\ListItemRun', $oListItemRun); - $this->assertCount(0, $oListItemRun->getElements()); - } - - /** - * New instance with array - */ - public function testConstructArray() - { - $oListItemRun = new ListItemRun(0, null, array('spacing' => 100)); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\ListItemRun', $oListItemRun); - $this->assertCount(0, $oListItemRun->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oListItemRun->getParagraphStyle()); - } - - /** - * Get style - */ - public function testStyle() - { - $oListItemRun = new ListItemRun(1, array('listType' => \PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER)); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\ListItem', $oListItemRun->getStyle()); - $this->assertEquals($oListItemRun->getStyle()->getListType(), \PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER); - } - /** - * getDepth - */ - public function testDepth() - { - $iVal = rand(1, 1000); - $oListItemRun = new ListItemRun($iVal); - - $this->assertEquals($oListItemRun->getDepth(), $iVal); - } - - /** - * Add text - */ - public function testAddText() - { - $oListItemRun = new ListItemRun(); - $element = $oListItemRun->addText('text'); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); - $this->assertCount(1, $oListItemRun->getElements()); - $this->assertEquals($element->getText(), 'text'); - } - - /** - * Add text non-UTF8 - */ - public function testAddTextNotUTF8() - { - $oListItemRun = new ListItemRun(); - $element = $oListItemRun->addText(utf8_decode('ééé')); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); - $this->assertCount(1, $oListItemRun->getElements()); - $this->assertEquals($element->getText(), 'ééé'); - } - - /** - * Add link - */ - public function testAddLink() - { - $oListItemRun = new ListItemRun(); - $element = $oListItemRun->addLink('http://www.google.fr/'); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); - $this->assertCount(1, $oListItemRun->getElements()); - $this->assertEquals($element->getSource(), 'http://www.google.fr/'); - } - - /** - * Add link with name - */ - public function testAddLinkWithName() - { - $oListItemRun = new ListItemRun(); - $element = $oListItemRun->addLink('http://www.google.fr/', utf8_decode('ééé')); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); - $this->assertCount(1, $oListItemRun->getElements()); - $this->assertEquals($element->getSource(), 'http://www.google.fr/'); - $this->assertEquals($element->getText(), 'ééé'); - } - - /** - * Add text break - */ - public function testAddTextBreak() - { - $oListItemRun = new ListItemRun(); - $oListItemRun->addTextBreak(2); - - $this->assertCount(2, $oListItemRun->getElements()); - } - - /** - * Add image - */ - public function testAddImage() - { - $src = __DIR__ . "/../_files/images/earth.jpg"; - - $oListItemRun = new ListItemRun(); - $element = $oListItemRun->addImage($src); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); - $this->assertCount(1, $oListItemRun->getElements()); - } -} diff --git a/tests/PhpWord/Tests/Element/ListItemTest.php b/tests/PhpWord/Tests/Element/ListItemTest.php deleted file mode 100644 index 1362eb4392..0000000000 --- a/tests/PhpWord/Tests/Element/ListItemTest.php +++ /dev/null @@ -1,69 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $oListItem->getTextObject()); - } - - /** - * Get style - */ - public function testStyle() - { - $oListItem = new ListItem( - 'text', - 1, - null, - array('listType' => \PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER) - ); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\ListItem', $oListItem->getStyle()); - $this->assertEquals( - $oListItem->getStyle()->getListType(), - \PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER - ); - } - - /** - * Get depth - */ - public function testDepth() - { - $iVal = rand(1, 1000); - $oListItem = new ListItem('text', $iVal); - - $this->assertEquals($oListItem->getDepth(), $iVal); - } -} diff --git a/tests/PhpWord/Tests/Element/ObjectTest.php b/tests/PhpWord/Tests/Element/ObjectTest.php deleted file mode 100644 index 6b82313453..0000000000 --- a/tests/PhpWord/Tests/Element/ObjectTest.php +++ /dev/null @@ -1,93 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Object', $oObject); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Image', $oObject->getStyle()); - $this->assertEquals($oObject->getSource(), $src); - } - - /** - * Create new instance with non-supported files - * - * @expectedException \PhpOffice\PhpWord\Exception\InvalidObjectException - */ - public function testConstructWithNotSupportedFiles() - { - $src = __DIR__ . "/../_files/xsl/passthrough.xsl"; - $oObject = new Object($src); - $oObject->getSource(); - } - - /** - * Create with style - */ - public function testConstructWithSupportedFilesAndStyle() - { - $src = __DIR__ . "/../_files/documents/sheet.xls"; - $oObject = new Object($src, array('width' => '230px')); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Object', $oObject); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Image', $oObject->getStyle()); - $this->assertEquals($oObject->getSource(), $src); - } - - /** - * Set/get relation Id - */ - public function testRelationId() - { - $src = __DIR__ . "/../_files/documents/sheet.xls"; - $oObject = new Object($src); - - $iVal = rand(1, 1000); - $oObject->setRelationId($iVal); - $this->assertEquals($oObject->getRelationId(), $iVal); - } - - /** - * Set/get image relation Id - */ - public function testImageRelationId() - { - $src = __DIR__ . "/../_files/documents/sheet.xls"; - $oObject = new Object($src); - - $iVal = rand(1, 1000); - $oObject->setImageRelationId($iVal); - $this->assertEquals($oObject->getImageRelationId(), $iVal); - } -} diff --git a/tests/PhpWord/Tests/Element/PreserveTextTest.php b/tests/PhpWord/Tests/Element/PreserveTextTest.php deleted file mode 100644 index 5d2e4f3e6f..0000000000 --- a/tests/PhpWord/Tests/Element/PreserveTextTest.php +++ /dev/null @@ -1,69 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $oPreserveText); - $this->assertEquals($oPreserveText->getText(), null); - $this->assertEquals($oPreserveText->getFontStyle(), null); - $this->assertEquals($oPreserveText->getParagraphStyle(), null); - } - - /** - * Create new instance with style name - */ - public function testConstructWithString() - { - $oPreserveText = new PreserveText('text', 'styleFont', 'styleParagraph'); - $this->assertEquals($oPreserveText->getText(), array('text')); - $this->assertEquals($oPreserveText->getFontStyle(), 'styleFont'); - $this->assertEquals($oPreserveText->getParagraphStyle(), 'styleParagraph'); - } - - /** - * Create new instance with array - */ - public function testConstructWithArray() - { - $oPreserveText = new PreserveText( - 'text', - array('align' => 'center'), - array('marginLeft' => 600, 'marginRight' => 600, 'marginTop' => 600, 'marginBottom' => 600) - ); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oPreserveText->getFontStyle()); - $this->assertInstanceOf( - 'PhpOffice\\PhpWord\\Style\\Paragraph', - $oPreserveText->getParagraphStyle() - ); - } -} diff --git a/tests/PhpWord/Tests/Element/RowTest.php b/tests/PhpWord/Tests/Element/RowTest.php deleted file mode 100644 index c377bb7cd2..0000000000 --- a/tests/PhpWord/Tests/Element/RowTest.php +++ /dev/null @@ -1,67 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Row', $oRow); - $this->assertEquals($oRow->getHeight(), null); - $this->assertInternalType('array', $oRow->getCells()); - $this->assertCount(0, $oRow->getCells()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Row', $oRow->getStyle()); - } - - /** - * Create new instance with parameters - */ - public function testConstructWithParams() - { - $iVal = rand(1, 1000); - $oRow = new Row($iVal, array('borderBottomSize' => 18, 'borderBottomColor' => '0000FF', 'bgColor' => '66BBFF')); - - $this->assertEquals($oRow->getHeight(), $iVal); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Row', $oRow->getStyle()); - } - - /** - * Add cell - */ - public function testAddCell() - { - $oRow = new Row(); - $element = $oRow->addCell(); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Cell', $element); - $this->assertCount(1, $oRow->getCells()); - } -} diff --git a/tests/PhpWord/Tests/Element/SDTTest.php b/tests/PhpWord/Tests/Element/SDTTest.php deleted file mode 100644 index 8c7b4bf328..0000000000 --- a/tests/PhpWord/Tests/Element/SDTTest.php +++ /dev/null @@ -1,69 +0,0 @@ -setValue($value); - $object->setListItems($types); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\SDT', $object); - $this->assertEquals($type, $object->getType()); - $this->assertEquals($types, $object->getListItems()); - $this->assertEquals($value, $object->getValue()); - } - - /** - * Test set type exception - * - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Invalid style value - */ - public function testSetTypeException() - { - $object = new SDT('comboBox'); - $object->setType('foo'); - } - - /** - * Test set type - */ - public function testSetTypeNull() - { - $object = new SDT('comboBox'); - $object->setType(' '); - - $this->assertEquals('comboBox', $object->getType()); - } -} diff --git a/tests/PhpWord/Tests/Element/SectionTest.php b/tests/PhpWord/Tests/Element/SectionTest.php deleted file mode 100644 index 853ebc5d3f..0000000000 --- a/tests/PhpWord/Tests/Element/SectionTest.php +++ /dev/null @@ -1,179 +0,0 @@ -assertAttributeEquals($oSection->getStyle(), 'style', new Section(0)); - } - - /** - * Get elements - */ - public function testGetElements() - { - $oSection = new Section(0); - $this->assertAttributeEquals($oSection->getElements(), 'elements', new Section(0)); - } - - /** - * Get footer - */ - public function testGetFooters() - { - $oSection = new Section(0); - $this->assertAttributeEquals($oSection->getFooters(), 'footers', new Section(0)); - } - - /** - * Get headers - */ - public function testGetHeaders() - { - $oSection = new Section(0); - $this->assertAttributeEquals($oSection->getHeaders(), 'headers', new Section(0)); - } - - /** - * Set settings - */ - public function testSetStyle() - { - $expected = 'landscape'; - $object = new Section(0); - $object->setStyle(array('orientation' => $expected, 'foo' => null)); - $this->assertEquals($expected, $object->getStyle()->getOrientation()); - } - - /** - * Add elements - */ - public function testAddElements() - { - $objectSource = __DIR__ . "/../_files/documents/reader.docx"; - $imageSource = __DIR__ . "/../_files/images/PhpWord.png"; - // $imageUrl = 'http://php.net//images/logos/php-med-trans-light.gif'; - - $section = new Section(0); - $section->setPhpWord(new PhpWord()); - $section->addText(utf8_decode('ä')); - $section->addLink(utf8_decode('http://xn--4caaa.com/'), utf8_decode('ä')); - $section->addTextBreak(); - $section->addPageBreak(); - $section->addTable(); - $section->addListItem(utf8_decode('ä')); - $section->addObject($objectSource); - $section->addImage($imageSource); - $section->addTitle(utf8_decode('ä'), 1); - $section->addTextRun(); - $section->addFootnote(); - $section->addCheckBox(utf8_decode('chkä'), utf8_decode('Contentä')); - $section->addTOC(); - - $elementCollection = $section->getElements(); - $elementTypes = array('Text', 'Link', 'TextBreak', 'PageBreak', - 'Table', 'ListItem', 'Object', 'Image', - 'Title', 'TextRun', 'Footnote', 'CheckBox', 'TOC'); - $elmCount = 0; - foreach ($elementTypes as $elementType) { - $this->assertInstanceOf("PhpOffice\\PhpWord\\Element\\{$elementType}", $elementCollection[$elmCount]); - $elmCount++; - } - } - - /** - * Test add object exception - * - * @expectedException \PhpOffice\PhpWord\Exception\InvalidObjectException - */ - public function testAddObjectException() - { - $source = __DIR__ . "/_files/xsl/passthrough.xsl"; - $section = new Section(0); - $section->addObject($source); - } - - /** - * Add title with predefined style - */ - public function testAddTitleWithStyle() - { - Style::addTitleStyle(1, array('size' => 14)); - $section = new Section(0); - $section->setPhpWord(new PhpWord()); - $section->addTitle('Test', 1); - $elementCollection = $section->getElements(); - - $this->assertInstanceOf("PhpOffice\\PhpWord\\Element\\Title", $elementCollection[0]); - } - - /** - * Create header footer - */ - public function testCreateHeaderFooter() - { - $object = new Section(0); - $elements = array('Header', 'Footer'); - - foreach ($elements as $element) { - $method = "create{$element}"; - $this->assertInstanceOf("PhpOffice\\PhpWord\\Element\\{$element}", $object->$method()); - } - $this->assertFalse($object->hasDifferentFirstPage()); - } - - /** - * Add header has different first page - */ - public function testHasDifferentFirstPage() - { - $object = new Section(1); - $header = $object->addHeader(); - $header->setType(Header::FIRST); - $this->assertTrue($object->hasDifferentFirstPage()); - } - - /** - * Add header exception - * - * @expectedException Exception - * @expectedExceptionMesssage Invalid header/footer type. - */ - public function testAddHeaderException() - { - $object = new Section(1); - $object->addHeader('ODD'); - } -} diff --git a/tests/PhpWord/Tests/Element/TableTest.php b/tests/PhpWord/Tests/Element/TableTest.php deleted file mode 100644 index 0977639f32..0000000000 --- a/tests/PhpWord/Tests/Element/TableTest.php +++ /dev/null @@ -1,114 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Table', $oTable); - $this->assertEquals($oTable->getStyle(), null); - $this->assertEquals($oTable->getWidth(), null); - $this->assertEquals($oTable->getRows(), array()); - $this->assertCount(0, $oTable->getRows()); - } - - /** - * Get style name - */ - public function testStyleText() - { - $oTable = new Table('tableStyle'); - - $this->assertEquals($oTable->getStyle(), 'tableStyle'); - } - - /** - * Get style array - */ - public function testStyleArray() - { - $oTable = new Table(array( - 'borderSize' => 6, - 'borderColor' => '006699', - 'cellMargin' => 80 - )); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Table', $oTable->getStyle()); - } - - /** - * Set/get width - */ - public function testWidth() - { - $oTable = new Table(); - $iVal = rand(1, 1000); - $oTable->setWidth($iVal); - $this->assertEquals($oTable->getWidth(), $iVal); - } - - /** - * Add/get row - */ - public function testRow() - { - $oTable = new Table(); - $element = $oTable->addRow(); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Row', $element); - $this->assertCount(1, $oTable->getRows()); - } - - /** - * Add cell - */ - public function testCell() - { - $oTable = new Table(); - $oTable->addRow(); - $element = $oTable->addCell(); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Cell', $element); - } - - /** - * Add cell - */ - public function testCountColumns() - { - $oTable = new Table(); - $oTable->addRow(); - $element = $oTable->addCell(); - $this->assertEquals($oTable->countColumns(), 1); - $element = $oTable->addCell(); - $element = $oTable->addCell(); - $this->assertEquals($oTable->countColumns(), 3); - } -} diff --git a/tests/PhpWord/Tests/Element/TextRunTest.php b/tests/PhpWord/Tests/Element/TextRunTest.php deleted file mode 100644 index de62a920da..0000000000 --- a/tests/PhpWord/Tests/Element/TextRunTest.php +++ /dev/null @@ -1,156 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $oTextRun); - $this->assertCount(0, $oTextRun->getElements()); - $this->assertEquals($oTextRun->getParagraphStyle(), null); - } - - /** - * New instance with string - */ - public function testConstructString() - { - $oTextRun = new TextRun('pStyle'); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $oTextRun); - $this->assertCount(0, $oTextRun->getElements()); - $this->assertEquals($oTextRun->getParagraphStyle(), 'pStyle'); - } - - /** - * New instance with array - */ - public function testConstructArray() - { - $oTextRun = new TextRun(array('spacing' => 100)); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $oTextRun); - $this->assertCount(0, $oTextRun->getElements()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oTextRun->getParagraphStyle()); - } - - /** - * Add text - */ - public function testAddText() - { - $oTextRun = new TextRun(); - $element = $oTextRun->addText('text'); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); - $this->assertCount(1, $oTextRun->getElements()); - $this->assertEquals($element->getText(), 'text'); - } - - /** - * Add text non-UTF8 - */ - public function testAddTextNotUTF8() - { - $oTextRun = new TextRun(); - $element = $oTextRun->addText(utf8_decode('ééé')); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); - $this->assertCount(1, $oTextRun->getElements()); - $this->assertEquals($element->getText(), 'ééé'); - } - - /** - * Add link - */ - public function testAddLink() - { - $oTextRun = new TextRun(); - $element = $oTextRun->addLink('http://www.google.fr/'); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); - $this->assertCount(1, $oTextRun->getElements()); - $this->assertEquals($element->getSource(), 'http://www.google.fr/'); - } - - /** - * Add link with name - */ - public function testAddLinkWithName() - { - $oTextRun = new TextRun(); - $element = $oTextRun->addLink('http://www.google.fr/', utf8_decode('ééé')); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); - $this->assertCount(1, $oTextRun->getElements()); - $this->assertEquals($element->getSource(), 'http://www.google.fr/'); - $this->assertEquals($element->getText(), 'ééé'); - } - - /** - * Add text break - */ - public function testAddTextBreak() - { - $oTextRun = new TextRun(); - $oTextRun->addTextBreak(2); - - $this->assertCount(2, $oTextRun->getElements()); - } - - /** - * Add image - */ - public function testAddImage() - { - $src = __DIR__ . "/../_files/images/earth.jpg"; - - $oTextRun = new TextRun(); - $element = $oTextRun->addImage($src); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); - $this->assertCount(1, $oTextRun->getElements()); - } - - /** - * Add footnote - */ - public function testCreateFootnote() - { - $oTextRun = new TextRun(); - $oTextRun->setPhpWord(new PhpWord()); - $element = $oTextRun->addFootnote(); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Footnote', $element); - $this->assertCount(1, $oTextRun->getElements()); - } -} diff --git a/tests/PhpWord/Tests/Element/TextTest.php b/tests/PhpWord/Tests/Element/TextTest.php deleted file mode 100644 index a306d4b938..0000000000 --- a/tests/PhpWord/Tests/Element/TextTest.php +++ /dev/null @@ -1,86 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $oText); - $this->assertEquals(null, $oText->getText()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oText->getFontStyle()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oText->getParagraphStyle()); - } - - /** - * Get text - */ - public function testText() - { - $oText = new Text('text'); - - $this->assertEquals($oText->getText(), 'text'); - } - - /** - * Get font style - */ - public function testFont() - { - $oText = new Text('text', 'fontStyle'); - $this->assertEquals($oText->getFontStyle(), 'fontStyle'); - - $oText->setFontStyle(array('bold' => true, 'italic' => true, 'size' => 16)); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oText->getFontStyle()); - } - - /** - * Get font style as object - */ - public function testFontObject() - { - $font = new Font(); - $oText = new Text('text', $font); - $this->assertEquals($oText->getFontStyle(), $font); - } - - /** - * Get paragraph style - */ - public function testParagraph() - { - $oText = new Text('text', 'fontStyle', 'paragraphStyle'); - $this->assertEquals($oText->getParagraphStyle(), 'paragraphStyle'); - - $oText->setParagraphStyle(array('align' => 'center', 'spaceAfter' => 100)); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oText->getParagraphStyle()); - } -} diff --git a/tests/PhpWord/Tests/Element/TitleTest.php b/tests/PhpWord/Tests/Element/TitleTest.php deleted file mode 100644 index ca65c8eb94..0000000000 --- a/tests/PhpWord/Tests/Element/TitleTest.php +++ /dev/null @@ -1,50 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\Element\\Title', $oTitle); - $this->assertEquals($oTitle->getText(), 'text'); - } - - /** - * Get style null - */ - public function testStyleNull() - { - $oTitle = new Title('text'); - - $this->assertEquals($oTitle->getStyle(), null); - } -} diff --git a/tests/PhpWord/Tests/IOFactoryTest.php b/tests/PhpWord/Tests/IOFactoryTest.php deleted file mode 100644 index 60db16d050..0000000000 --- a/tests/PhpWord/Tests/IOFactoryTest.php +++ /dev/null @@ -1,83 +0,0 @@ -assertInstanceOf( - 'PhpOffice\\PhpWord\\Writer\\Word2007', - IOFactory::createWriter(new PhpWord(), 'Word2007') - ); - } - - /** - * Create non-existing writer - * - * @expectedException \PhpOffice\PhpWord\Exception\Exception - */ - public function testNonexistentWriterCanNotBeCreated() - { - IOFactory::createWriter(new PhpWord(), 'Word2006'); - } - - /** - * Create existing reader - */ - public function testExistingReaderCanBeCreated() - { - $this->assertInstanceOf( - 'PhpOffice\\PhpWord\\Reader\\Word2007', - IOFactory::createReader('Word2007') - ); - } - - /** - * Create non-existing reader - * - * @expectedException \PhpOffice\PhpWord\Exception\Exception - */ - public function testNonexistentReaderCanNotBeCreated() - { - IOFactory::createReader('Word2006'); - } - - /** - * Load document - */ - public function testLoad() - { - $file = __DIR__ . "/_files/templates/blank.docx"; - $this->assertInstanceOf( - 'PhpOffice\\PhpWord\\PhpWord', - IOFactory::load($file) - ); - } -} diff --git a/tests/PhpWord/Tests/MediaTest.php b/tests/PhpWord/Tests/MediaTest.php deleted file mode 100644 index 0196a7e1a2..0000000000 --- a/tests/PhpWord/Tests/MediaTest.php +++ /dev/null @@ -1,117 +0,0 @@ -assertEquals(Media::getElements('section'), array()); - } - - /** - * Count section media elements - */ - public function testCountSectionMediaElementsWithNull() - { - $this->assertEquals(0, Media::countElements('section')); - } - - /** - * Add section media element - */ - public function testAddSectionMediaElement() - { - $local = __DIR__ . "/_files/images/mars.jpg"; - $object = __DIR__ . "/_files/documents/sheet.xls"; - $remote = 'http://php.net/images/logos/php-med-trans-light.gif'; - Media::addElement('section', 'image', $local, new Image($local)); - Media::addElement('section', 'image', $local, new Image($local)); - Media::addElement('section', 'image', $remote, new Image($local)); - Media::addElement('section', 'object', $object); - Media::addElement('section', 'object', $object); - - $this->assertCount(3, Media::getElements('section')); - } - - /** - * Add section link - */ - public function testAddSectionLinkElement() - { - $expected = Media::countElements('section') + 1; - $actual = Media::addElement('section', 'link', 'http://test.com/'); - - $this->assertEquals($expected, $actual); - $this->assertCount(1, Media::getElements('section', 'link')); - } - - /** - * Add header media element - */ - public function testAddHeaderMediaElement() - { - $local = __DIR__ . "/_files/images/mars.jpg"; - $remote = 'http://php.net/images/logos/php-med-trans-light.gif'; - Media::addElement('header1', 'image', $local, new Image($local)); - Media::addElement('header1', 'image', $local, new Image($local)); - Media::addElement('header1', 'image', $remote, new Image($remote)); - - $this->assertCount(2, Media::getElements('header1')); - $this->assertEmpty(Media::getElements('header2')); - } - - /** - * Add footer media element and reset media - */ - public function testAddFooterMediaElement() - { - $local = __DIR__ . "/_files/images/mars.jpg"; - $remote = 'http://php.net/images/logos/php-med-trans-light.gif'; - Media::addElement('footer1', 'image', $local, new Image($local)); - Media::addElement('footer1', 'image', $local, new Image($local)); - Media::addElement('footer1', 'image', $remote, new Image($remote)); - - $this->assertCount(2, Media::getElements('footer1')); - - Media::resetElements(); - $this->assertCount(0, Media::getElements('footer1')); - } - - /** - * Add image element exception - * - * @expectedException Exception - * @expectedExceptionMessage Image object not assigned. - */ - public function testAddElementImageException() - { - Media::addElement('section', 'image', __DIR__ . "/_files/images/mars.jpg"); - } -} diff --git a/tests/PhpWord/Tests/Metadata/DocInfoTest.php b/tests/PhpWord/Tests/Metadata/DocInfoTest.php deleted file mode 100644 index c860a0d9a9..0000000000 --- a/tests/PhpWord/Tests/Metadata/DocInfoTest.php +++ /dev/null @@ -1,274 +0,0 @@ -setCreator(); - $this->assertEquals('', $oProperties->getCreator()); - - $oProperties->setCreator('AAA'); - $this->assertEquals('AAA', $oProperties->getCreator()); - } - - /** - * Last modified by - */ - public function testLastModifiedBy() - { - $oProperties = new DocInfo(); - $oProperties->setLastModifiedBy(); - $this->assertEquals('', $oProperties->getLastModifiedBy()); - - $oProperties->setLastModifiedBy('AAA'); - $this->assertEquals('AAA', $oProperties->getLastModifiedBy()); - } - - /** - * Created - */ - public function testCreated() - { - $oProperties = new DocInfo(); - $oProperties->setCreated(); - $this->assertEquals(time(), $oProperties->getCreated()); - - $iTime = time() + 3600; - $oProperties->setCreated($iTime); - $this->assertEquals($iTime, $oProperties->getCreated()); - } - - /** - * Modified - */ - public function testModified() - { - $oProperties = new DocInfo(); - $oProperties->setModified(); - $this->assertEquals(time(), $oProperties->getModified()); - - $iTime = time() + 3600; - $oProperties->setModified($iTime); - $this->assertEquals($iTime, $oProperties->getModified()); - } - - /** - * Title - */ - public function testTitle() - { - $oProperties = new DocInfo(); - $oProperties->setTitle(); - $this->assertEquals('', $oProperties->getTitle()); - - $oProperties->setTitle('AAA'); - $this->assertEquals('AAA', $oProperties->getTitle()); - } - - /** - * Description - */ - public function testDescription() - { - $oProperties = new DocInfo(); - $oProperties->setDescription(); - $this->assertEquals('', $oProperties->getDescription()); - - $oProperties->setDescription('AAA'); - $this->assertEquals('AAA', $oProperties->getDescription()); - } - - /** - * Subject - */ - public function testSubject() - { - $oProperties = new DocInfo(); - $oProperties->setSubject(); - $this->assertEquals('', $oProperties->getSubject()); - - $oProperties->setSubject('AAA'); - $this->assertEquals('AAA', $oProperties->getSubject()); - } - - /** - * Keywords - */ - public function testKeywords() - { - $oProperties = new DocInfo(); - $oProperties->setKeywords(); - $this->assertEquals('', $oProperties->getKeywords()); - - $oProperties->setKeywords('AAA'); - $this->assertEquals('AAA', $oProperties->getKeywords()); - } - - /** - * Category - */ - public function testCategory() - { - $oProperties = new DocInfo(); - $oProperties->setCategory(); - $this->assertEquals('', $oProperties->getCategory()); - - $oProperties->setCategory('AAA'); - $this->assertEquals('AAA', $oProperties->getCategory()); - } - - /** - * Company - */ - public function testCompany() - { - $oProperties = new DocInfo(); - $oProperties->setCompany(); - $this->assertEquals('', $oProperties->getCompany()); - - $oProperties->setCompany('AAA'); - $this->assertEquals('AAA', $oProperties->getCompany()); - } - - /** - * Manager - */ - public function testManager() - { - $oProperties = new DocInfo(); - $oProperties->setManager(); - $this->assertEquals('', $oProperties->getManager()); - - $oProperties->setManager('AAA'); - $this->assertEquals('AAA', $oProperties->getManager()); - } - - /** - * Custom properties - */ - public function testCustomProperty() - { - $oProperties = new DocInfo(); - $oProperties->setCustomProperty('key1', null); - $oProperties->setCustomProperty('key2', true); - $oProperties->setCustomProperty('key3', 3); - $oProperties->setCustomProperty('key4', 4.4); - $oProperties->setCustomProperty('key5', 'value5'); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_STRING, - $oProperties->getCustomPropertyType('key1') - ); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_BOOLEAN, - $oProperties->getCustomPropertyType('key2') - ); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_INTEGER, - $oProperties->getCustomPropertyType('key3') - ); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_FLOAT, - $oProperties->getCustomPropertyType('key4') - ); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_STRING, - $oProperties->getCustomPropertyType('key5') - ); - $this->assertEquals(null, $oProperties->getCustomPropertyType('key6')); - $this->assertEquals(null, $oProperties->getCustomPropertyValue('key1')); - $this->assertEquals(true, $oProperties->getCustomPropertyValue('key2')); - $this->assertEquals(3, $oProperties->getCustomPropertyValue('key3')); - $this->assertEquals(4.4, $oProperties->getCustomPropertyValue('key4')); - $this->assertEquals('value5', $oProperties->getCustomPropertyValue('key5')); - $this->assertEquals(null, $oProperties->getCustomPropertyValue('key6')); - $this->assertEquals(true, $oProperties->isCustomPropertySet('key5')); - $this->assertEquals(false, $oProperties->isCustomPropertySet('key6')); - $this->assertEquals(array( - 'key1', - 'key2', - 'key3', - 'key4', - 'key5' - ), $oProperties->getCustomProperties()); - } - - /** - * Convert property - */ - public function testConvertProperty() - { - $this->assertEquals('', DocInfo::convertProperty('a', 'empty')); - $this->assertEquals(null, DocInfo::convertProperty('a', 'null')); - $this->assertEquals(8, DocInfo::convertProperty('8', 'int')); - $this->assertEquals(8, DocInfo::convertProperty('8.3', 'uint')); - $this->assertEquals(8.3, DocInfo::convertProperty('8.3', 'decimal')); - $this->assertEquals('8.3', DocInfo::convertProperty('8.3', 'lpstr')); - $this->assertEquals(strtotime('10/11/2013'), DocInfo::convertProperty('10/11/2013', 'date')); - $this->assertEquals(true, DocInfo::convertProperty('true', 'bool')); - $this->assertEquals(false, DocInfo::convertProperty('1', 'bool')); - $this->assertEquals('1', DocInfo::convertProperty('1', 'array')); - $this->assertEquals('1', DocInfo::convertProperty('1', '')); - - - $this->assertEquals( - DocInfo::PROPERTY_TYPE_INTEGER, - DocInfo::convertPropertyType('int') - ); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_INTEGER, - DocInfo::convertPropertyType('uint') - ); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_FLOAT, - DocInfo::convertPropertyType('decimal') - ); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_STRING, - DocInfo::convertPropertyType('lpstr') - ); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_DATE, - DocInfo::convertPropertyType('date') - ); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_BOOLEAN, - DocInfo::convertPropertyType('bool') - ); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_UNKNOWN, - DocInfo::convertPropertyType('array') - ); - $this->assertEquals( - DocInfo::PROPERTY_TYPE_UNKNOWN, - DocInfo::convertPropertyType('') - ); - } -} diff --git a/tests/PhpWord/Tests/PhpWordTest.php b/tests/PhpWord/Tests/PhpWordTest.php deleted file mode 100644 index 85c6a7f291..0000000000 --- a/tests/PhpWord/Tests/PhpWordTest.php +++ /dev/null @@ -1,176 +0,0 @@ -assertEquals(new DocInfo(), $phpWord->getDocInfo()); - $this->assertEquals(Settings::DEFAULT_FONT_NAME, $phpWord->getDefaultFontName()); - $this->assertEquals(Settings::DEFAULT_FONT_SIZE, $phpWord->getDefaultFontSize()); - } - - /** - * Test create/get section - */ - public function testCreateGetSections() - { - $phpWord = new PhpWord(); - $phpWord->addSection(); - $this->assertCount(1, $phpWord->getSections()); - } - - /** - * Test set/get default font name - */ - public function testSetGetDefaultFontName() - { - $phpWord = new PhpWord(); - $fontName = 'Times New Roman'; - $this->assertEquals(Settings::DEFAULT_FONT_NAME, $phpWord->getDefaultFontName()); - $phpWord->setDefaultFontName($fontName); - $this->assertEquals($fontName, $phpWord->getDefaultFontName()); - } - - /** - * Test set/get default font size - */ - public function testSetGetDefaultFontSize() - { - $phpWord = new PhpWord(); - $fontSize = 16; - $this->assertEquals(Settings::DEFAULT_FONT_SIZE, $phpWord->getDefaultFontSize()); - $phpWord->setDefaultFontSize($fontSize); - $this->assertEquals($fontSize, $phpWord->getDefaultFontSize()); - } - - /** - * Test set default paragraph style - */ - public function testSetDefaultParagraphStyle() - { - $phpWord = new PhpWord(); - $phpWord->setDefaultParagraphStyle(array()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', Style::getStyle('Normal')); - } - - /** - * Test add styles - */ - public function testAddStyles() - { - $phpWord = new PhpWord(); - $styles = array( - 'Paragraph' => 'Paragraph', - 'Font' => 'Font', - 'Table' => 'Table', - 'Link' => 'Font', - ); - foreach ($styles as $key => $value) { - $method = "add{$key}Style"; - $styleId = "{$key} Style"; - $phpWord->$method($styleId, array()); - $this->assertInstanceOf("PhpOffice\\PhpWord\\Style\\{$value}", Style::getStyle($styleId)); - } - - } - - /** - * Test add title style - */ - public function testAddTitleStyle() - { - $phpWord = new PhpWord(); - $titleLevel = 1; - $titleName = "Heading_{$titleLevel}"; - $phpWord->addTitleStyle($titleLevel, array()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', Style::getStyle($titleName)); - } - - /** - * Test load template - * - * @deprecated 0.12.0 - */ - public function testLoadTemplate() - { - $templateFqfn = __DIR__ . "/_files/templates/blank.docx"; - - $phpWord = new PhpWord(); - $this->assertInstanceOf( - 'PhpOffice\\PhpWord\\TemplateProcessor', - $phpWord->loadTemplate($templateFqfn) - ); - } - - /** - * Test load template exception - * - * @deprecated 0.12.0 - * - * @expectedException \PhpOffice\PhpWord\Exception\Exception - */ - public function testLoadTemplateException() - { - $templateFqfn = join( - DIRECTORY_SEPARATOR, - array(PHPWORD_TESTS_BASE_DIR, 'PhpWord', 'Tests', '_files', 'templates', 'blanks.docx') - ); - $phpWord = new PhpWord(); - $phpWord->loadTemplate($templateFqfn); - } - - /** - * Test save - */ - public function testSave() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - $section->addText('Hello world!'); - - $this->assertTrue($phpWord->save('test.docx', 'Word2007', true)); - } - - /** - * Test calling undefined method - * - * @expectedException \BadMethodCallException - * @expectedExceptionMessage is not defined - */ - public function testCallUndefinedMethod() - { - $phpWord = new PhpWord(); - $phpWord->undefinedMethod(); - } -} diff --git a/tests/PhpWord/Tests/Reader/Word2007Test.php b/tests/PhpWord/Tests/Reader/Word2007Test.php deleted file mode 100644 index f2257012a5..0000000000 --- a/tests/PhpWord/Tests/Reader/Word2007Test.php +++ /dev/null @@ -1,60 +0,0 @@ -assertTrue($object->canRead($filename)); - } - - /** - * Can read exception - */ - public function testCanReadFailed() - { - $object = new Word2007(); - $filename = __DIR__ . '/../_files/documents/foo.docx'; - $this->assertFalse($object->canRead($filename)); - } - - /** - * Load - */ - public function testLoad() - { - $filename = __DIR__ . '/../_files/documents/reader.docx'; - $phpWord = IOFactory::load($filename); - $this->assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); - } -} diff --git a/tests/PhpWord/Tests/SettingsTest.php b/tests/PhpWord/Tests/SettingsTest.php deleted file mode 100644 index 0d3d66aecc..0000000000 --- a/tests/PhpWord/Tests/SettingsTest.php +++ /dev/null @@ -1,142 +0,0 @@ -assertTrue(Settings::hasCompatibility()); - $this->assertTrue(Settings::setCompatibility(false)); - $this->assertFalse(Settings::hasCompatibility()); - } - - /** - * Test set/get zip class - */ - public function testSetGetZipClass() - { - $this->assertEquals(Settings::ZIPARCHIVE, Settings::getZipClass()); - $this->assertTrue(Settings::setZipClass(Settings::PCLZIP)); - $this->assertFalse(Settings::setZipClass('foo')); - } - - /** - * Test set/get PDF renderer - */ - public function testSetGetPdfRenderer() - { - $domPdfPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); - - $this->assertFalse(Settings::setPdfRenderer('FOO', 'dummy/path')); - $this->assertTrue(Settings::setPdfRenderer(Settings::PDF_RENDERER_DOMPDF, $domPdfPath)); - $this->assertEquals(Settings::PDF_RENDERER_DOMPDF, Settings::getPdfRendererName()); - $this->assertEquals($domPdfPath, Settings::getPdfRendererPath()); - $this->assertFalse(Settings::setPdfRendererPath('dummy/path')); - } - - /** - * Test set/get measurement unit - */ - public function testSetGetMeasurementUnit() - { - $this->assertEquals(Settings::UNIT_TWIP, Settings::getMeasurementUnit()); - $this->assertTrue(Settings::setMeasurementUnit(Settings::UNIT_INCH)); - $this->assertFalse(Settings::setMeasurementUnit('foo')); - } - - /** - * @covers ::getTempDir - * @test - */ - public function testPhpTempDirIsUsedByDefault() - { - $this->assertEquals(sys_get_temp_dir(), Settings::getTempDir()); - } - - - /** - * @covers ::setTempDir - * @covers ::getTempDir - * @depends testPhpTempDirIsUsedByDefault - * @test - */ - public function testTempDirCanBeSet() - { - $userDefinedTempDir = 'C:\PhpWordTemp'; - Settings::setTempDir($userDefinedTempDir); - $currentTempDir = Settings::getTempDir(); - $this->assertEquals($userDefinedTempDir, $currentTempDir); - $this->assertNotEquals(sys_get_temp_dir(), $currentTempDir); - } - - /** - * Test set/get default font name - */ - public function testSetGetDefaultFontName() - { - $this->assertEquals(Settings::DEFAULT_FONT_NAME, Settings::getDefaultFontName()); - $this->assertTrue(Settings::setDefaultFontName('Times New Roman')); - $this->assertFalse(Settings::setDefaultFontName(' ')); - } - - /** - * Test set/get default font size - */ - public function testSetGetDefaultFontSize() - { - $this->assertEquals(Settings::DEFAULT_FONT_SIZE, Settings::getDefaultFontSize()); - $this->assertTrue(Settings::setDefaultFontSize(12)); - $this->assertFalse(Settings::setDefaultFontSize(null)); - } - - /** - * Test load config - */ - public function testLoadConfig() - { - $expected = array( - 'compatibility' => true, - 'zipClass' => 'ZipArchive', - 'pdfRendererName' => 'DomPDF', - 'pdfRendererPath' => '', - 'defaultFontName' => 'Arial', - 'defaultFontSize' => 10, - ); - - // Test default value - $this->assertEquals($expected, Settings::loadConfig()); - - // Test with valid file - $this->assertEquals($expected, Settings::loadConfig(__DIR__ . '/../../../phpword.ini.dist')); - - // Test with invalid file - $this->assertEmpty(Settings::loadConfig(__DIR__ . '/../../../phpunit.xml.dist')); - } -} diff --git a/tests/PhpWord/Tests/Shared/ConverterTest.php b/tests/PhpWord/Tests/Shared/ConverterTest.php deleted file mode 100644 index 002e2e33bf..0000000000 --- a/tests/PhpWord/Tests/Shared/ConverterTest.php +++ /dev/null @@ -1,117 +0,0 @@ -assertEquals($value / 2.54 * 1440, $result); - - $result = Converter::cmToInch($value); - $this->assertEquals($value / 2.54, $result); - - $result = Converter::cmToPixel($value); - $this->assertEquals($value / 2.54 * 96, $result); - - $result = Converter::cmToPoint($value); - $this->assertEquals($value / 2.54 * 72, $result); - - $result = Converter::cmToEmu($value); - $this->assertEquals(round($value / 2.54 * 96 * 9525), $result); - - $result = Converter::inchToTwip($value); - $this->assertEquals($value * 1440, $result); - - $result = Converter::inchToCm($value); - $this->assertEquals($value * 2.54, $result); - - $result = Converter::inchToPixel($value); - $this->assertEquals($value * 96, $result); - - $result = Converter::inchToPoint($value); - $this->assertEquals($value * 72, $result); - - $result = Converter::inchToEmu($value); - $this->assertEquals(round($value * 96 * 9525), $result); - - $result = Converter::pixelToTwip($value); - $this->assertEquals($value / 96 * 1440, $result); - - $result = Converter::pixelToCm($value); - $this->assertEquals($value / 96 * 2.54, $result); - - $result = Converter::pixelToPoint($value); - $this->assertEquals($value / 96 * 72, $result); - - $result = Converter::pixelToEMU($value); - $this->assertEquals(round($value * 9525), $result); - - $result = Converter::pointToTwip($value); - $this->assertEquals($value * 20, $result); - - $result = Converter::pointToPixel($value); - $this->assertEquals($value / 72 * 96, $result); - - $result = Converter::pointToEMU($value); - $this->assertEquals(round($value / 72 * 96 * 9525), $result); - - $result = Converter::emuToPixel($value); - $this->assertEquals(round($value / 9525), $result); - - $result = Converter::degreeToAngle($value); - $this->assertEquals((int)round($value * 60000), $result); - - $result = Converter::angleToDegree($value); - $this->assertEquals(round($value / 60000), $result); - } - } - - /** - * Test htmlToRGB() - */ - public function testHtmlToRGB() - { - // Prepare test values [ original, expected ] - $values[] = array('#FF99DD', array(255, 153, 221)); // With # - $values[] = array('FF99DD', array(255, 153, 221)); // 6 characters - $values[] = array('F9D', array(255, 153, 221)); // 3 characters - $values[] = array('0F9D', false); // 4 characters - // Conduct test - foreach ($values as $value) { - $result = Converter::htmlToRGB($value[0]); - $this->assertEquals($value[1], $result); - } - } -} diff --git a/tests/PhpWord/Tests/Shared/HtmlTest.php b/tests/PhpWord/Tests/Shared/HtmlTest.php deleted file mode 100644 index c4c0fc9d5a..0000000000 --- a/tests/PhpWord/Tests/Shared/HtmlTest.php +++ /dev/null @@ -1,71 +0,0 @@ -assertCount(0, $section->getElements()); - - // Heading - $styles = array('strong', 'em', 'sup', 'sub'); - for ($level = 1; $level <= 6; $level++) { - $content .= "Heading {$level}"; - } - - // Styles - $content .= '

      '; - foreach ($styles as $style) { - $content .= "<{$style}>{$style}"; - } - $content .= '

      '; - - // Add HTML - Html::addHtml($section, $content); - $this->assertCount(7, $section->getElements()); - - // Other parts - $section = new Section(1); - $content = ''; - $content .= '
      HeaderContent
      '; - $content .= '
      • Bullet
        • Bullet
      '; - $content .= '
      1. Bullet
      '; - $content .= "'Single Quoted Text'"; - $content .= '"Double Quoted Text"'; - $content .= '& Ampersand'; - $content .= '<>“‘’«»‹›'; - $content .= '&•°…™©®—'; - $content .= '–   ²³¼½¾'; - Html::addHtml($section, $content); - } -} diff --git a/tests/PhpWord/Tests/Shared/StringTest.php b/tests/PhpWord/Tests/Shared/StringTest.php deleted file mode 100644 index a3524eded8..0000000000 --- a/tests/PhpWord/Tests/Shared/StringTest.php +++ /dev/null @@ -1,75 +0,0 @@ -assertTrue(String::isUTF8('')); - $this->assertTrue(String::isUTF8('éééé')); - $this->assertFalse(String::isUTF8(utf8_decode('éééé'))); - } - - /** - * OOXML to PHP control character - */ - public function testControlCharacterOOXML2PHP() - { - $this->assertEquals('', String::controlCharacterOOXML2PHP('')); - $this->assertEquals(chr(0x08), String::controlCharacterOOXML2PHP('_x0008_')); - } - - /** - * PHP to OOXML control character - */ - public function testControlCharacterPHP2OOXML() - { - $this->assertEquals('', String::controlCharacterPHP2OOXML('')); - $this->assertEquals('_x0008_', String::controlCharacterPHP2OOXML(chr(0x08))); - } - - /** - * Test unicode conversion - */ - public function testToUnicode() - { - $this->assertEquals('a', String::toUnicode('a')); - $this->assertEquals('\uc0{\u8364}', String::toUnicode('€')); - $this->assertEquals('\uc0{\u233}', String::toUnicode('é')); - } - - /** - * Test remove underscore prefix - */ - public function testRemoveUnderscorePrefix() - { - $this->assertEquals('item', String::removeUnderscorePrefix('_item')); - } -} diff --git a/tests/PhpWord/Tests/Shared/XMLReaderTest.php b/tests/PhpWord/Tests/Shared/XMLReaderTest.php deleted file mode 100644 index 2bb6ef658d..0000000000 --- a/tests/PhpWord/Tests/Shared/XMLReaderTest.php +++ /dev/null @@ -1,75 +0,0 @@ -getDomFromZip($filename, 'yadayadaya'); - } - - /** - * Test get DOMDocument from ZipArchive returns false - */ - public function testGetDomFromZipReturnsFalse() - { - $filename = __DIR__ . "/../_files/documents/reader.docx.zip"; - $object = new XMLReader(); - $this->assertFalse($object->getDomFromZip($filename, 'yadayadaya')); - } - - /** - * Test get elements returns empty - */ - public function testGetElementsReturnsEmpty() - { - $object = new XMLReader(); - $this->assertEquals(array(), $object->getElements('w:document')); - } - - /** - * Test get element returns null - */ - public function testGetElementReturnsNull() - { - $filename = __DIR__ . "/../_files/documents/reader.docx.zip"; - - $object = new XMLReader(); - $object->getDomFromZip($filename, '[Content_Types].xml'); - $element = $object->getElements('*')->item(0); - - $this->assertNull($object->getElement('yadayadaya', $element)); - } -} diff --git a/tests/PhpWord/Tests/Shared/ZipArchiveTest.php b/tests/PhpWord/Tests/Shared/ZipArchiveTest.php deleted file mode 100644 index 11a0d9a33c..0000000000 --- a/tests/PhpWord/Tests/Shared/ZipArchiveTest.php +++ /dev/null @@ -1,136 +0,0 @@ -open($zipFile, ZipArchive::CREATE); - // $object->addFromString('content/string.txt', 'Test'); - - // // Lock the file - // $resource = fopen($zipFile, "w"); - // flock($resource, LOCK_EX); - - // // Closing the file should throws an exception - // $object->close(); - - // // Unlock the file - // flock($resource, LOCK_UN); - // fclose($resource); - - // @unlink($zipFile); - } - - /** - * Test all methods - * - * @param string $zipClass - * @covers :: - */ - public function testZipArchive($zipClass = 'ZipArchive') - { - // Preparation - $existingFile = __DIR__ . "/../_files/documents/sheet.xls"; - $zipFile = __DIR__ . "/../_files/documents/ziptest.zip"; - $destination1 = __DIR__ . "/../_files/documents/extract1"; - $destination2 = __DIR__ . "/../_files/documents/extract2"; - @mkdir($destination1); - @mkdir($destination2); - - Settings::setZipClass($zipClass); - - $object = new ZipArchive(); - $object->open($zipFile, ZipArchive::CREATE); - $object->addFile($existingFile, 'xls/new.xls'); - $object->addFromString('content/string.txt', 'Test'); - $object->close(); - $object->open($zipFile); - - // Run tests - $this->assertEquals(0, $object->locateName('xls/new.xls')); - $this->assertFalse($object->locateName('blablabla')); - - $this->assertEquals('Test', $object->getFromName('content/string.txt')); - $this->assertEquals('Test', $object->getFromName('/content/string.txt')); - - $this->assertFalse($object->getNameIndex(-1)); - $this->assertEquals('content/string.txt', $object->getNameIndex(1)); - - $this->assertFalse($object->extractTo('blablabla')); - $this->assertTrue($object->extractTo($destination1)); - $this->assertTrue($object->extractTo($destination2, 'xls/new.xls')); - $this->assertFalse($object->extractTo($destination2, 'blablabla')); - - // Cleanup - $this->deleteDir($destination1); - $this->deleteDir($destination2); - @unlink($zipFile); - } - - /** - * Test PclZip - * - * @covers :: - */ - public function testPCLZip() - { - $this->testZipArchive('PhpOffice\PhpWord\Shared\ZipArchive'); - } - - /** - * Delete directory - * - * @param string $dir - */ - private function deleteDir($dir) - { - foreach (scandir($dir) as $file) { - if ($file === '.' || $file === '..') { - continue; - } elseif (is_file($dir . "/" . $file)) { - unlink($dir . "/" . $file); - } elseif (is_dir($dir . "/" . $file)) { - $this->deleteDir($dir . "/" . $file); - } - } - - rmdir($dir); - } -} diff --git a/tests/PhpWord/Tests/Style/AbstractStyleTest.php b/tests/PhpWord/Tests/Style/AbstractStyleTest.php deleted file mode 100644 index d35e109076..0000000000 --- a/tests/PhpWord/Tests/Style/AbstractStyleTest.php +++ /dev/null @@ -1,91 +0,0 @@ -getMockForAbstractClass('\PhpOffice\PhpWord\Style\AbstractStyle'); - $stub->setStyleByArray(array('index' => 1)); - - $this->assertEquals(1, $stub->getIndex()); - } - - /** - * Test setBoolVal, setIntVal, setFloatVal, setEnumVal with normal value - */ - public function testSetValNormal() - { - $stub = $this->getMockForAbstractClass('\PhpOffice\PhpWord\Style\AbstractStyle'); - - $this->assertEquals(true, self::callProtectedMethod($stub, 'setBoolVal', array(true, false))); - $this->assertEquals(12, self::callProtectedMethod($stub, 'setIntVal', array(12, 200))); - $this->assertEquals(871.1, self::callProtectedMethod($stub, 'setFloatVal', array(871.1, 2.1))); - $this->assertEquals(871.1, self::callProtectedMethod($stub, 'setFloatVal', array('871.1', 2.1))); - $this->assertEquals('a', self::callProtectedMethod($stub, 'setEnumVal', array('a', array('a', 'b'), 'b'))); - } - - /** - * Test setBoolVal, setIntVal, setFloatVal, setEnumVal with default value - */ - public function testSetValDefault() - { - $stub = $this->getMockForAbstractClass('\PhpOffice\PhpWord\Style\AbstractStyle'); - - $this->assertEquals(false, self::callProtectedMethod($stub, 'setBoolVal', array('a', false))); - $this->assertEquals(200, self::callProtectedMethod($stub, 'setIntVal', array('foo', 200))); - $this->assertEquals(2.1, self::callProtectedMethod($stub, 'setFloatVal', array('foo', 2.1))); - $this->assertEquals('b', self::callProtectedMethod($stub, 'setEnumVal', array(null, array('a', 'b'), 'b'))); - } - - /** - * Test setEnumVal exception - * - * @expectedException \InvalidArgumentException - */ - public function testSetValEnumException() - { - $stub = $this->getMockForAbstractClass('\PhpOffice\PhpWord\Style\AbstractStyle'); - - $this->assertEquals('b', self::callProtectedMethod($stub, 'setEnumVal', array('z', array('a', 'b'), 'b'))); - } - - /** - * Helper function to call protected method - * - * @param mixed $object - * @param string $method - * @param array $args - */ - public static function callProtectedMethod($object, $method, array $args = array()) - { - $class = new \ReflectionClass(get_class($object)); - $method = $class->getMethod($method); - $method->setAccessible(true); - return $method->invokeArgs($object, $args); - } -} diff --git a/tests/PhpWord/Tests/Style/CellTest.php b/tests/PhpWord/Tests/Style/CellTest.php deleted file mode 100644 index f913172806..0000000000 --- a/tests/PhpWord/Tests/Style/CellTest.php +++ /dev/null @@ -1,90 +0,0 @@ - Cell::VALIGN_TOP, - 'textDirection' => Cell::TEXT_DIR_BTLR, - 'bgColor' => 'FFFF00', - 'borderTopSize' => 120, - 'borderTopColor' => 'FFFF00', - 'borderLeftSize' => 120, - 'borderLeftColor' => 'FFFF00', - 'borderRightSize' => 120, - 'borderRightColor' => 'FFFF00', - 'borderBottomSize' => 120, - 'borderBottomColor' => 'FFFF00', - 'gridSpan' => 2, - 'vMerge' => Cell::VMERGE_RESTART, - ); - foreach ($attributes as $key => $value) { - $set = "set{$key}"; - $get = "get{$key}"; - - $this->assertNull($object->$get()); // Init with null value - - $object->$set($value); - - $this->assertEquals($value, $object->$get()); - } - } - - /** - * Test border color - */ - public function testBorderColor() - { - $object = new Cell(); - - $value = 'FF0000'; - - $object->setStyleValue('borderColor', $value); - $expected = array($value, $value, $value, $value); - $this->assertEquals($expected, $object->getBorderColor()); - } - - /** - * Test border size - */ - public function testBorderSize() - { - $object = new Cell(); - - $value = 120; - $expected = array($value, $value, $value, $value); - $object->setStyleValue('borderSize', $value); - $this->assertEquals($expected, $object->getBorderSize()); - } -} diff --git a/tests/PhpWord/Tests/Style/FontTest.php b/tests/PhpWord/Tests/Style/FontTest.php deleted file mode 100644 index c21ea4ce3e..0000000000 --- a/tests/PhpWord/Tests/Style/FontTest.php +++ /dev/null @@ -1,178 +0,0 @@ - 'both')); - - $this->assertEquals('text', $object->getStyleType()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $object->getParagraphStyle()); - $this->assertTrue(is_array($object->getStyleValues())); - } - - /** - * Test setting style values with null or empty value - */ - public function testSetStyleValueWithNullOrEmpty() - { - $object = new Font(); - - $attributes = array( - 'name' => null, - 'size' => null, - 'hint' => null, - 'color' => null, - 'bold' => false, - 'italic' => false, - 'underline' => Font::UNDERLINE_NONE, - 'superScript' => false, - 'subScript' => false, - 'strikethrough' => false, - 'doubleStrikethrough' => false, - 'smallCaps' => false, - 'allCaps' => false, - 'fgColor' => null, - 'bgColor' => null, - 'scale' => null, - 'spacing' => null, - 'kerning' => null, - ); - foreach ($attributes as $key => $default) { - $get = is_bool($default) ? "is{$key}" : "get{$key}"; - $this->assertEquals($default, $object->$get()); - $object->setStyleValue("$key", null); - $this->assertEquals($default, $object->$get()); - $object->setStyleValue("$key", ''); - $this->assertEquals($default, $object->$get()); - } - } - - /** - * Test setting style values with normal value - */ - public function testSetStyleValueNormal() - { - $object = new Font(); - - $attributes = array( - 'name' => 'Times New Roman', - 'size' => 9, - 'color' => '999999', - 'hint' => 'eastAsia', - 'bold' => true, - 'italic' => true, - 'underline' => Font::UNDERLINE_HEAVY, - 'superScript' => true, - 'subScript' => false, - 'strikethrough' => true, - 'doubleStrikethrough' => false, - 'smallCaps' => true, - 'allCaps' => false, - 'fgColor' => Font::FGCOLOR_YELLOW, - 'bgColor' => 'FFFF00', - 'lineHeight' => 2, - 'scale' => 150, - 'spacing' => 240, - 'kerning' => 10, - ); - $object->setStyleByArray($attributes); - foreach ($attributes as $key => $value) { - $get = is_bool($value) ? "is{$key}" : "get{$key}"; - $this->assertEquals($value, $object->$get()); - } - } - - /** - * Test set line height - */ - public function testLineHeight() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - - // Test style array - $text = $section->addText('This is a test', array( - 'line-height' => 2.0 - )); - - $doc = TestHelperDOCX::getDocument($phpWord); - $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:spacing'); - - $lineHeight = $element->getAttribute('w:line'); - $lineRule = $element->getAttribute('w:lineRule'); - - $this->assertEquals(480, $lineHeight); - $this->assertEquals('auto', $lineRule); - - // Test setter - $text->getFontStyle()->setLineHeight(3.0); - $doc = TestHelperDOCX::getDocument($phpWord); - $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:spacing'); - - $lineHeight = $element->getAttribute('w:line'); - $lineRule = $element->getAttribute('w:lineRule'); - - $this->assertEquals(720, $lineHeight); - $this->assertEquals('auto', $lineRule); - } - - /** - * Test line height floatval - */ - public function testLineHeightFloatval() - { - $object = new Font(null, array('align' => 'center')); - $object->setLineHeight('1.5pt'); - $this->assertEquals(1.5, $object->getLineHeight()); - } - - /** - * Test line height exception by using nonnumeric value - * - * @expectedException \PhpOffice\PhpWord\Exception\InvalidStyleException - */ - public function testLineHeightException() - { - $object = new Font(); - $object->setLineHeight('a'); - } -} diff --git a/tests/PhpWord/Tests/Style/ImageTest.php b/tests/PhpWord/Tests/Style/ImageTest.php deleted file mode 100644 index 1a679da3a2..0000000000 --- a/tests/PhpWord/Tests/Style/ImageTest.php +++ /dev/null @@ -1,89 +0,0 @@ - 200, - 'height' => 200, - 'align' => 'left', - 'marginTop' => 240, - 'marginLeft' => 240, - 'wrappingStyle' => 'inline' - ); - foreach ($properties as $key => $value) { - $set = "set{$key}"; - $get = "get{$key}"; - $object->$set($value); - $this->assertEquals($value, $object->$get()); - } - } - - /** - * Test setStyleValue method - */ - public function testSetStyleValue() - { - $object = new Image(); - - $properties = array( - 'width' => 200, - 'height' => 200, - 'align' => 'left', - 'marginTop' => 240, - 'marginLeft' => 240, - 'positioning' => \PhpOffice\PhpWord\Style\Image::POSITION_ABSOLUTE, - 'posHorizontal' => \PhpOffice\PhpWord\Style\Image::POSITION_HORIZONTAL_CENTER, - 'posVertical' => \PhpOffice\PhpWord\Style\Image::POSITION_VERTICAL_TOP, - 'posHorizontalRel' => \PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_COLUMN, - 'posVerticalRel' => \PhpOffice\PhpWord\Style\Image::POSITION_RELATIVE_TO_IMARGIN - ); - foreach ($properties as $key => $value) { - $get = "get{$key}"; - $object->setStyleValue("{$key}", $value); - $this->assertEquals($value, $object->$get()); - } - } - - /** - * Test setWrappingStyle exception - * - * @expectedException \InvalidArgumentException - */ - public function testSetWrappingStyleException() - { - $object = new Image(); - $object->setWrappingStyle('foo'); - } -} diff --git a/tests/PhpWord/Tests/Style/LineNumberingTest.php b/tests/PhpWord/Tests/Style/LineNumberingTest.php deleted file mode 100644 index bc4dc603f3..0000000000 --- a/tests/PhpWord/Tests/Style/LineNumberingTest.php +++ /dev/null @@ -1,53 +0,0 @@ - array(1, 2), - 'increment' => array(1, 10), - 'distance' => array(null, 10), - 'restart' => array(null, 'continuous'), - ); - foreach ($properties as $property => $value) { - list($default, $expected) = $value; - $get = "get{$property}"; - $set = "set{$property}"; - - $this->assertEquals($default, $object->$get()); // Default value - - $object->$set($expected); - - $this->assertEquals($expected, $object->$get()); // New value - } - } -} diff --git a/tests/PhpWord/Tests/Style/LineTest.php b/tests/PhpWord/Tests/Style/LineTest.php deleted file mode 100644 index 02d5ba16a2..0000000000 --- a/tests/PhpWord/Tests/Style/LineTest.php +++ /dev/null @@ -1,151 +0,0 @@ - \PhpOffice\PhpWord\Style\Line::CONNECTOR_TYPE_STRAIGHT, - 'beginArrow' => \PhpOffice\PhpWord\Style\Line::ARROW_STYLE_BLOCK, - 'endArrow' => \PhpOffice\PhpWord\Style\Line::ARROW_STYLE_OVAL, - 'dash' => \PhpOffice\PhpWord\Style\Line::DASH_STYLE_LONG_DASH_DOT_DOT, - 'weight' => 10, - 'color' => 'red' - ); - foreach ($properties as $key => $value) { - $set = "set{$key}"; - $get = "get{$key}"; - $object->$set($value); - $this->assertEquals($value, $object->$get()); - } - } - - /** - * Test setStyleValue method - */ - public function testSetStyleValue() - { - $object = new Line(); - - $properties = array( - 'connectorType' => \PhpOffice\PhpWord\Style\Line::CONNECTOR_TYPE_STRAIGHT, - 'beginArrow' => \PhpOffice\PhpWord\Style\Line::ARROW_STYLE_BLOCK, - 'endArrow' => \PhpOffice\PhpWord\Style\Line::ARROW_STYLE_OVAL, - 'dash' => \PhpOffice\PhpWord\Style\Line::DASH_STYLE_LONG_DASH_DOT_DOT, - 'weight' => 10, - 'color' => 'red' - ); - foreach ($properties as $key => $value) { - $get = "get{$key}"; - $object->setStyleValue("{$key}", $value); - $this->assertEquals($value, $object->$get()); - } - } - - /** - * Test set/get flip - */ - public function testSetGetFlip() - { - $expected=true; - $object = new Line(); - $object->setFlip($expected); - $this->assertEquals($expected, $object->isFlip()); - } - - /** - * Test set/get connectorType - */ - public function testSetGetConnectorType() - { - $expected=\PhpOffice\PhpWord\Style\Line::CONNECTOR_TYPE_STRAIGHT; - $object = new Line(); - $object->setConnectorType($expected); - $this->assertEquals($expected, $object->getConnectorType()); - } - - /** - * Test set/get weight - */ - public function testSetGetWeight() - { - $expected=10; - $object = new Line(); - $object->setWeight($expected); - $this->assertEquals($expected, $object->getWeight()); - } - - /** - * Test set/get color - */ - public function testSetGetColor() - { - $expected='red'; - $object = new Line(); - $object->setColor($expected); - $this->assertEquals($expected, $object->getColor()); - } - - /** - * Test set/get dash - */ - public function testSetGetDash() - { - $expected=\PhpOffice\PhpWord\Style\Line::DASH_STYLE_LONG_DASH_DOT_DOT; - $object = new Line(); - $object->setDash($expected); - $this->assertEquals($expected, $object->getDash()); - } - - /** - * Test set/get beginArrow - */ - public function testSetGetBeginArrow() - { - $expected=\PhpOffice\PhpWord\Style\Line::ARROW_STYLE_BLOCK; - $object = new Line(); - $object->setBeginArrow($expected); - $this->assertEquals($expected, $object->getBeginArrow()); - } - - /** - * Test set/get endArrow - */ - public function testSetGetEndArrow() - { - $expected=\PhpOffice\PhpWord\Style\Line::ARROW_STYLE_CLASSIC; - $object = new Line(); - $object->setEndArrow($expected); - $this->assertEquals($expected, $object->getEndArrow()); - } -} diff --git a/tests/PhpWord/Tests/Style/NumberingTest.php b/tests/PhpWord/Tests/Style/NumberingTest.php deleted file mode 100644 index e15753576b..0000000000 --- a/tests/PhpWord/Tests/Style/NumberingTest.php +++ /dev/null @@ -1,61 +0,0 @@ -object = new Numbering(); - $this->properties = array( - 'numId' => array(null, 1), - 'type' => array(null, 'singleLevel'), - ); - foreach ($this->properties as $property => $value) { - list($default, $expected) = $value; - $get = "get{$property}"; - $set = "set{$property}"; - - $this->assertEquals($default, $this->object->$get()); // Default value - - $this->object->$set($expected); - - $this->assertEquals($expected, $this->object->$get()); // New value - } - } - - /** - * Test get level - */ - public function testGetLevels() - { - $this->object = new Numbering(); - - $this->assertEmpty($this->object->getLevels()); - } -} diff --git a/tests/PhpWord/Tests/Style/ParagraphTest.php b/tests/PhpWord/Tests/Style/ParagraphTest.php deleted file mode 100644 index 73540b0c7b..0000000000 --- a/tests/PhpWord/Tests/Style/ParagraphTest.php +++ /dev/null @@ -1,179 +0,0 @@ - true, - 'keepNext' => false, - 'keepLines' => false, - 'pageBreakBefore' => false, - ); - foreach ($attributes as $key => $default) { - $get = "get{$key}"; - $object->setStyleValue("$key", null); - $this->assertEquals($default, $object->$get()); - $object->setStyleValue("$key", ''); - $this->assertEquals($default, $object->$get()); - } - } - - /** - * Test setting style values with normal value - */ - public function testSetStyleValueNormal() - { - $object = new Paragraph(); - - $attributes = array( - 'align' => 'justify', - 'spaceAfter' => 240, - 'spaceBefore' => 240, - 'indent' => 1, - 'hanging' => 1, - 'spacing' => 120, - 'basedOn' => 'Normal', - 'next' => 'Normal', - 'numStyle' => 'numStyle', - 'numLevel' => 1, - 'widowControl' => false, - 'keepNext' => true, - 'keepLines' => true, - 'pageBreakBefore' => true, - ); - foreach ($attributes as $key => $value) { - $get = "get{$key}"; - $object->setStyleValue("$key", $value); - if ($key == 'align') { - if ($value == 'justify') { - $value = 'both'; - } - } elseif ($key == 'indent' || $key == 'hanging') { - $value = $value * 720; - } elseif ($key == 'spacing') { - $value += 240; - } - $this->assertEquals($value, $object->$get()); - } - } - - /** - * Test get null style value - */ - public function testGetNullStyleValue() - { - $object = new Paragraph(); - - $attributes = array('spacing', 'indent', 'hanging', 'spaceBefore', 'spaceAfter'); - foreach ($attributes as $key) { - $get = "get{$key}"; - $this->assertNull($object->$get()); - } - } - - /** - * Test tabs - */ - public function testTabs() - { - $object = new Paragraph(); - $object->setTabs(array(new Tab('left', 1550), new Tab('right', 5300))); - $this->assertCount(2, $object->getTabs()); - } - - /** - * Line height - */ - public function testLineHeight() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - - // Test style array - $text = $section->addText('This is a test', array(), array( - 'line-height' => 2.0 - )); - - $doc = TestHelperDOCX::getDocument($phpWord); - $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:spacing'); - - $lineHeight = $element->getAttribute('w:line'); - $lineRule = $element->getAttribute('w:lineRule'); - - $this->assertEquals(480, $lineHeight); - $this->assertEquals('auto', $lineRule); - - // Test setter - $text->getParagraphStyle()->setLineHeight(3.0); - $doc = TestHelperDOCX::getDocument($phpWord); - $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:spacing'); - - $lineHeight = $element->getAttribute('w:line'); - $lineRule = $element->getAttribute('w:lineRule'); - - $this->assertEquals(720, $lineHeight); - $this->assertEquals('auto', $lineRule); - } - - /** - * Test setLineHeight validation - */ - public function testLineHeightValidation() - { - $object = new Paragraph(); - $object->setLineHeight('12.5pt'); - $this->assertEquals(12.5, $object->getLineHeight()); - } - - /** - * Test line height exception by using nonnumeric value - * - * @expectedException \PhpOffice\PhpWord\Exception\InvalidStyleException - */ - public function testLineHeightException() - { - $object = new Paragraph(); - $object->setLineHeight('a'); - } -} diff --git a/tests/PhpWord/Tests/Style/SectionTest.php b/tests/PhpWord/Tests/Style/SectionTest.php deleted file mode 100644 index e5becd7296..0000000000 --- a/tests/PhpWord/Tests/Style/SectionTest.php +++ /dev/null @@ -1,326 +0,0 @@ -assertEquals('portrait', $oSettings->getOrientation()); - $this->assertEquals(Section::DEFAULT_WIDTH, $oSettings->getPageSizeW()); - $this->assertEquals(Section::DEFAULT_HEIGHT, $oSettings->getPageSizeH()); - $this->assertEquals('A4', $oSettings->getPaperSize()); - - $oSettings->setSettingValue('orientation', 'landscape'); - $this->assertEquals('landscape', $oSettings->getOrientation()); - $this->assertEquals(Section::DEFAULT_HEIGHT, $oSettings->getPageSizeW()); - $this->assertEquals(Section::DEFAULT_WIDTH, $oSettings->getPageSizeH()); - - $iVal = rand(1, 1000); - $oSettings->setSettingValue('borderSize', $iVal); - $this->assertEquals(array($iVal, $iVal, $iVal, $iVal), $oSettings->getBorderSize()); - $this->assertEquals($iVal, $oSettings->getBorderBottomSize()); - $this->assertEquals($iVal, $oSettings->getBorderLeftSize()); - $this->assertEquals($iVal, $oSettings->getBorderRightSize()); - $this->assertEquals($iVal, $oSettings->getBorderTopSize()); - - $oSettings->setSettingValue('borderColor', 'FF00AA'); - $this->assertEquals(array('FF00AA', 'FF00AA', 'FF00AA', 'FF00AA'), $oSettings->getBorderColor()); - $this->assertEquals('FF00AA', $oSettings->getBorderBottomColor()); - $this->assertEquals('FF00AA', $oSettings->getBorderLeftColor()); - $this->assertEquals('FF00AA', $oSettings->getBorderRightColor()); - $this->assertEquals('FF00AA', $oSettings->getBorderTopColor()); - - $iVal = rand(1, 1000); - $oSettings->setSettingValue('headerHeight', $iVal); - $this->assertEquals($iVal, $oSettings->getHeaderHeight()); - - $oSettings->setSettingValue('lineNumbering', array()); - $oSettings->setSettingValue('lineNumbering', array('start' => 1, 'increment' => 1, - 'distance' => 240, 'restart' => 'newPage')); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\LineNumbering', $oSettings->getLineNumbering()); - - $oSettings->setSettingValue('lineNumbering', null); - $this->assertNull($oSettings->getLineNumbering()); - } - - /** - * Set/get margin - */ - public function testMargin() - { - // Section Settings - $oSettings = new Section(); - - $iVal = rand(1, 1000); - $oSettings->setMarginTop($iVal); - $this->assertEquals($iVal, $oSettings->getMarginTop()); - - $iVal = rand(1, 1000); - $oSettings->setMarginBottom($iVal); - $this->assertEquals($iVal, $oSettings->getMarginBottom()); - - $iVal = rand(1, 1000); - $oSettings->setMarginLeft($iVal); - $this->assertEquals($iVal, $oSettings->getMarginLeft()); - - $iVal = rand(1, 1000); - $oSettings->setMarginRight($iVal); - $this->assertEquals($iVal, $oSettings->getMarginRight()); - } - - /** - * Set/get page width - */ - public function testPageWidth() - { - // Section Settings - $oSettings = new Section(); - - $this->assertEquals(Section::DEFAULT_WIDTH, $oSettings->getPageSizeW()); - $iVal = rand(1, 1000); - $oSettings->setSettingValue('pageSizeW', $iVal); - $this->assertEquals($iVal, $oSettings->getPageSizeW()); - } - - /** - * Set/get page height - */ - public function testPageHeight() - { - // Section Settings - $oSettings = new Section(); - - $this->assertEquals(Section::DEFAULT_HEIGHT, $oSettings->getPageSizeH()); - $iVal = rand(1, 1000); - $oSettings->setSettingValue('pageSizeH', $iVal); - $this->assertEquals($iVal, $oSettings->getPageSizeH()); - } - - /** - * Set/get landscape orientation - */ - public function testOrientationLandscape() - { - // Section Settings - $oSettings = new Section(); - - $oSettings->setLandscape(); - $this->assertEquals('landscape', $oSettings->getOrientation()); - $this->assertEquals(Section::DEFAULT_HEIGHT, $oSettings->getPageSizeW()); - $this->assertEquals(Section::DEFAULT_WIDTH, $oSettings->getPageSizeH()); - } - - /** - * Set/get portrait orientation - */ - public function testOrientationPortrait() - { - // Section Settings - $oSettings = new Section(); - - $oSettings->setPortrait(); - $this->assertEquals('portrait', $oSettings->getOrientation()); - $this->assertEquals(Section::DEFAULT_WIDTH, $oSettings->getPageSizeW()); - $this->assertEquals(Section::DEFAULT_HEIGHT, $oSettings->getPageSizeH()); - } - - /** - * Set/get border size - */ - public function testBorderSize() - { - // Section Settings - $oSettings = new Section(); - - $iVal = rand(1, 1000); - $oSettings->setBorderSize($iVal); - $this->assertEquals(array($iVal, $iVal, $iVal, $iVal), $oSettings->getBorderSize()); - $this->assertEquals($iVal, $oSettings->getBorderBottomSize()); - $this->assertEquals($iVal, $oSettings->getBorderLeftSize()); - $this->assertEquals($iVal, $oSettings->getBorderRightSize()); - $this->assertEquals($iVal, $oSettings->getBorderTopSize()); - - $iVal = rand(1, 1000); - $oSettings->setBorderBottomSize($iVal); - $this->assertEquals($iVal, $oSettings->getBorderBottomSize()); - - $iVal = rand(1, 1000); - $oSettings->setBorderLeftSize($iVal); - $this->assertEquals($iVal, $oSettings->getBorderLeftSize()); - - $iVal = rand(1, 1000); - $oSettings->setBorderRightSize($iVal); - $this->assertEquals($iVal, $oSettings->getBorderRightSize()); - - $iVal = rand(1, 1000); - $oSettings->setBorderTopSize($iVal); - $this->assertEquals($iVal, $oSettings->getBorderTopSize()); - } - - /** - * Set/get border color - */ - public function testBorderColor() - { - // Section Settings - $oSettings = new Section(); - - $oSettings->setBorderColor('FF00AA'); - $this->assertEquals(array('FF00AA', 'FF00AA', 'FF00AA', 'FF00AA'), $oSettings->getBorderColor()); - $this->assertEquals('FF00AA', $oSettings->getBorderBottomColor()); - $this->assertEquals('FF00AA', $oSettings->getBorderLeftColor()); - $this->assertEquals('FF00AA', $oSettings->getBorderRightColor()); - $this->assertEquals('FF00AA', $oSettings->getBorderTopColor()); - - $oSettings->setBorderBottomColor('BBCCDD'); - $this->assertEquals('BBCCDD', $oSettings->getBorderBottomColor()); - - $oSettings->setBorderLeftColor('CCDDEE'); - $this->assertEquals('CCDDEE', $oSettings->getBorderLeftColor()); - - $oSettings->setBorderRightColor('11EE22'); - $this->assertEquals('11EE22', $oSettings->getBorderRightColor()); - - $oSettings->setBorderTopColor('22FF33'); - $this->assertEquals('22FF33', $oSettings->getBorderTopColor()); - } - - /** - * Set/get page numbering start - */ - public function testNumberingStart() - { - // Section Settings - $oSettings = new Section(); - - $this->assertNull($oSettings->getPageNumberingStart()); - - $iVal = rand(1, 1000); - $oSettings->setPageNumberingStart($iVal); - $this->assertEquals($iVal, $oSettings->getPageNumberingStart()); - - $oSettings->setPageNumberingStart(); - $this->assertNull($oSettings->getPageNumberingStart()); - } - - /** - * Set/get header height - */ - public function testHeader() - { - $oSettings = new Section(); - - $this->assertEquals(720, $oSettings->getHeaderHeight()); - - $iVal = rand(1, 1000); - $oSettings->setHeaderHeight($iVal); - $this->assertEquals($iVal, $oSettings->getHeaderHeight()); - - $oSettings->setHeaderHeight(); - $this->assertEquals(720, $oSettings->getHeaderHeight()); - } - - /** - * Set/get footer height - */ - public function testFooter() - { - // Section Settings - $oSettings = new Section(); - - $this->assertEquals(720, $oSettings->getFooterHeight()); - - $iVal = rand(1, 1000); - $oSettings->setFooterHeight($iVal); - $this->assertEquals($iVal, $oSettings->getFooterHeight()); - - $oSettings->setFooterHeight(); - $this->assertEquals(720, $oSettings->getFooterHeight()); - } - - /** - * Set/get column number - */ - public function testColumnsNum() - { - // Section Settings - $oSettings = new Section(); - - // Default - $this->assertEquals(1, $oSettings->getColsNum()); - - // Null value - $oSettings->setColsNum(); - $this->assertEquals(1, $oSettings->getColsNum()); - - // Random value - $iVal = rand(1, 1000); - $oSettings->setColsNum($iVal); - $this->assertEquals($iVal, $oSettings->getColsNum()); - } - - /** - * Set/get column spacing - */ - public function testColumnsSpace() - { - // Section Settings - $oSettings = new Section(); - - // Default - $this->assertEquals(720, $oSettings->getColsSpace()); - - $iVal = rand(1, 1000); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Section', $oSettings->setColsSpace($iVal)); - $this->assertEquals($iVal, $oSettings->getColsSpace()); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Section', $oSettings->setColsSpace()); - $this->assertEquals(720, $oSettings->getColsSpace()); - } - - /** - * Set/get break type - */ - public function testBreakType() - { - // Section Settings - $oSettings = new Section(); - - $this->assertNull($oSettings->getBreakType()); - - $oSettings->setBreakType('continuous'); - $this->assertEquals('continuous', $oSettings->getBreakType()); - - $oSettings->setBreakType(); - $this->assertNull($oSettings->getBreakType()); - } -} diff --git a/tests/PhpWord/Tests/Style/SpacingTest.php b/tests/PhpWord/Tests/Style/SpacingTest.php deleted file mode 100644 index a4022b74bd..0000000000 --- a/tests/PhpWord/Tests/Style/SpacingTest.php +++ /dev/null @@ -1,53 +0,0 @@ - array(null, 10), - 'after' => array(null, 10), - 'line' => array(null, 10), - 'rule' => array('auto', 'exact'), - ); - foreach ($properties as $property => $value) { - list($default, $expected) = $value; - $get = "get{$property}"; - $set = "set{$property}"; - - $this->assertEquals($default, $object->$get()); // Default value - - $object->$set($expected); - - $this->assertEquals($expected, $object->$get()); // New value - } - } -} diff --git a/tests/PhpWord/Tests/Style/TOCTest.php b/tests/PhpWord/Tests/Style/TOCTest.php deleted file mode 100644 index e6e32e6b9d..0000000000 --- a/tests/PhpWord/Tests/Style/TOCTest.php +++ /dev/null @@ -1,52 +0,0 @@ - array(TOC::TAB_LEADER_DOT, TOC::TAB_LEADER_UNDERSCORE), - 'tabPos' => array(9062, 10), - 'indent' => array(200, 10), - ); - foreach ($properties as $property => $value) { - list($default, $expected) = $value; - $get = "get{$property}"; - $set = "set{$property}"; - - $this->assertEquals($default, $object->$get()); // Default value - - $object->$set($expected); - - $this->assertEquals($expected, $object->$get()); // New value - } - } -} diff --git a/tests/PhpWord/Tests/Style/TabTest.php b/tests/PhpWord/Tests/Style/TabTest.php deleted file mode 100644 index 784b4e4709..0000000000 --- a/tests/PhpWord/Tests/Style/TabTest.php +++ /dev/null @@ -1,52 +0,0 @@ - array(Tab::TAB_STOP_CLEAR, Tab::TAB_STOP_RIGHT), - 'leader' => array(Tab::TAB_LEADER_NONE, Tab::TAB_LEADER_DOT), - 'position' => array(0, 10), - ); - foreach ($properties as $property => $value) { - list($default, $expected) = $value; - $get = "get{$property}"; - $set = "set{$property}"; - - $this->assertEquals($default, $object->$get()); // Default value - - $object->$set($expected); - - $this->assertEquals($expected, $object->$get()); // New value - } - } -} diff --git a/tests/PhpWord/Tests/Style/TableTest.php b/tests/PhpWord/Tests/Style/TableTest.php deleted file mode 100644 index 2afbab744a..0000000000 --- a/tests/PhpWord/Tests/Style/TableTest.php +++ /dev/null @@ -1,178 +0,0 @@ - 'FF0000'); - $styleFirstRow = array('borderBottomSize' => 3); - - $object = new Table(); - $this->assertNull($object->getBgColor()); - - $object = new Table($styleTable, $styleFirstRow); - $this->assertEquals('FF0000', $object->getBgColor()); - - $firstRow = $object->getFirstRow(); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Table', $firstRow); - $this->assertEquals(3, $firstRow->getBorderBottomSize()); - } - - /** - * Test setting style with normal value - */ - public function testSetGetNormal() - { - $object = new Table(); - - $attributes = array( - 'bgColor' => 'FF0000', - 'borderTopSize' => 4, - 'borderTopColor' => 'FF0000', - 'borderLeftSize' => 4, - 'borderLeftColor' => 'FF0000', - 'borderRightSize' => 4, - 'borderRightColor' => 'FF0000', - 'borderBottomSize' => 4, - 'borderBottomColor' => 'FF0000', - 'borderInsideHSize' => 4, - 'borderInsideHColor' => 'FF0000', - 'borderInsideVSize' => 4, - 'borderInsideVColor' => 'FF0000', - 'cellMarginTop' => 240, - 'cellMarginLeft' => 240, - 'cellMarginRight' => 240, - 'cellMarginBottom' => 240, - 'align' => 'center', - 'width' => 100, - 'unit' => 'pct', - ); - foreach ($attributes as $key => $value) { - $set = "set{$key}"; - $get = "get{$key}"; - $object->$set($value); - $this->assertEquals($value, $object->$get()); - } - } - - /** - * Test border color - * - * Set border color and test if each part has the same color - * While looping, push values array to be asserted with getBorderColor - */ - public function testBorderColor() - { - $object = new Table(); - $parts = array('Top', 'Left', 'Right', 'Bottom', 'InsideH', 'InsideV'); - - $value = 'FF0000'; - $object->setBorderColor($value); - foreach ($parts as $part) { - $get = "getBorder{$part}Color"; - $values[] = $value; - $this->assertEquals($value, $object->$get()); - } - $this->assertEquals($values, $object->getBorderColor()); - } - - /** - * Test border size - * - * Set border size and test if each part has the same size - * While looping, push values array to be asserted with getBorderSize - * Value is in eights of a point, i.e. 4 / 8 = .5pt - */ - public function testBorderSize() - { - $object = new Table(); - $parts = array('Top', 'Left', 'Right', 'Bottom', 'InsideH', 'InsideV'); - - $value = 4; - $object->setBorderSize($value); - foreach ($parts as $part) { - $get = "getBorder{$part}Size"; - $values[] = $value; - $this->assertEquals($value, $object->$get()); - } - $this->assertEquals($values, $object->getBorderSize()); - } - - /** - * Test cell margin - * - * Set cell margin and test if each part has the same margin - * While looping, push values array to be asserted with getCellMargin - * Value is in twips - */ - public function testCellMargin() - { - $object = new Table(); - $parts = array('Top', 'Left', 'Right', 'Bottom'); - - $value = 240; - $object->setCellMargin($value); - foreach ($parts as $part) { - $get = "getCellMargin{$part}"; - $values[] = $value; - $this->assertEquals($value, $object->$get()); - } - $this->assertEquals($values, $object->getCellMargin()); - $this->assertTrue($object->hasMargin()); - } - - /** - * Set style value for various special value types - */ - public function testSetStyleValue() - { - $object = new Table(); - $object->setStyleValue('borderSize', 120); - $object->setStyleValue('cellMargin', 240); - $object->setStyleValue('borderColor', '999999'); - - $this->assertEquals( - array(120, 120, 120, 120, 120, 120), - $object->getBorderSize() - ); - $this->assertEquals( - array(240, 240, 240, 240), - $object->getCellMargin() - ); - $this->assertEquals( - array('999999', '999999', '999999', '999999', '999999', '999999'), - $object->getBorderColor() - ); - } -} diff --git a/tests/PhpWord/Tests/Style/TextBoxTest.php b/tests/PhpWord/Tests/Style/TextBoxTest.php deleted file mode 100644 index cd2d86958d..0000000000 --- a/tests/PhpWord/Tests/Style/TextBoxTest.php +++ /dev/null @@ -1,305 +0,0 @@ - 200, - 'height' => 200, - 'align' => 'left', - 'marginTop' => 240, - 'marginLeft' => 240, - 'wrappingStyle' => 'inline', - 'positioning' => 'absolute', - 'posHorizontal' => 'center', - 'posVertical' => 'top', - 'posHorizontalRel' => 'margin', - 'posVerticalRel' => 'page', - 'innerMarginTop' => '5', - 'innerMarginRight' => '5', - 'innerMarginBottom' => '5', - 'innerMarginLeft' => '5', - 'borderSize' => '2', - 'borderColor' => 'red' - ); - foreach ($properties as $key => $value) { - $set = "set{$key}"; - $get = "get{$key}"; - $object->$set($value); - $this->assertEquals($value, $object->$get()); - } - } - - /** - * Test setStyleValue method - */ - public function testSetStyleValue() - { - $object = new TextBox(); - - $properties = array( - 'width' => 200, - 'height' => 200, - 'align' => 'left', - 'marginTop' => 240, - 'marginLeft' => 240, - 'wrappingStyle' => 'inline', - 'positioning' => 'absolute', - 'posHorizontal' => 'center', - 'posVertical' => 'top', - 'posHorizontalRel' => 'margin', - 'posVerticalRel' => 'page', - 'innerMarginTop' => '5', - 'innerMarginRight' => '5', - 'innerMarginBottom' => '5', - 'innerMarginLeft' => '5', - 'borderSize' => '2', - 'borderColor' => 'red' - ); - foreach ($properties as $key => $value) { - $get = "get{$key}"; - $object->setStyleValue("{$key}", $value); - $this->assertEquals($value, $object->$get()); - } - } - - /** - * Test setWrappingStyle exception - * - * @expectedException \InvalidArgumentException - */ - public function testSetWrappingStyleException() - { - $object = new TextBox(); - $object->setWrappingStyle('foo'); - } - - /** - * Test set/get width - */ - public function testSetGetWidth() - { - $expected=200; - $object = new TextBox(); - $object->setWidth($expected); - $this->assertEquals($expected, $object->getWidth()); - } - - /** - * Test set/get height - */ - public function testSetGetHeight() - { - $expected=200; - $object = new TextBox(); - $object->setHeight($expected); - $this->assertEquals($expected, $object->getHeight()); - } - - /** - * Test set/get height - */ - public function testSetGetAlign() - { - $expected='left'; - $object = new TextBox(); - $object->setAlign($expected); - $this->assertEquals($expected, $object->getAlign()); - } - - /** - * Test set/get marginTop - */ - public function testSetGetMarginTop() - { - $expected=5; - $object = new TextBox(); - $object->setMarginTop($expected); - $this->assertEquals($expected, $object->getMarginTop()); - } - - /** - * Test set/get marginLeft - */ - public function testSetGetMarginLeft() - { - $expected=5; - $object = new TextBox(); - $object->setMarginLeft($expected); - $this->assertEquals($expected, $object->getMarginLeft()); - } - /** - * Test set/get innerMarginTop - */ - public function testSetGetInnerMarginTop() - { - $expected=5; - $object = new TextBox(); - $object->setInnerMarginTop($expected); - $this->assertEquals($expected, $object->getInnerMarginTop()); - } - - /** - * Test set/get wrappingStyle - */ - public function testSetGetWrappingStyle() - { - $expected='inline'; - $object = new TextBox(); - $object->setWrappingStyle($expected); - $this->assertEquals($expected, $object->getWrappingStyle()); - } - - /** - * Test set/get positioning - */ - public function testSetGetPositioning() - { - $expected='absolute'; - $object = new TextBox(); - $object->setPositioning($expected); - $this->assertEquals($expected, $object->getPositioning()); - } - - /** - * Test set/get posHorizontal - */ - public function testSetGetPosHorizontal() - { - $expected='center'; - $object = new TextBox(); - $object->setPosHorizontal($expected); - $this->assertEquals($expected, $object->getPosHorizontal()); - } - - /** - * Test set/get posVertical - */ - public function testSetGetPosVertical() - { - $expected='top'; - $object = new TextBox(); - $object->setPosVertical($expected); - $this->assertEquals($expected, $object->getPosVertical()); - } - - /** - * Test set/get posHorizontalRel - */ - public function testSetGetPosHorizontalRel() - { - $expected='margin'; - $object = new TextBox(); - $object->setPosHorizontalRel($expected); - $this->assertEquals($expected, $object->getPosHorizontalRel()); - } - - /** - * Test set/get posVerticalRel - */ - public function testSetGetPosVerticalRel() - { - $expected='page'; - $object = new TextBox(); - $object->setPosVerticalRel($expected); - $this->assertEquals($expected, $object->getPosVerticalRel()); - } - - - /** - * Test set/get innerMarginRight - */ - public function testSetGetInnerMarginRight() - { - $expected=5; - $object = new TextBox(); - $object->setInnerMarginRight($expected); - $this->assertEquals($expected, $object->getInnerMarginRight()); - } - - /** - * Test set/get innerMarginBottom - */ - public function testSetGetInnerMarginBottom() - { - $expected=5; - $object = new TextBox(); - $object->setInnerMarginBottom($expected); - $this->assertEquals($expected, $object->getInnerMarginBottom()); - } - - /** - * Test set/get innerMarginLeft - */ - public function testSetGetInnerMarginLeft() - { - $expected=5; - $object = new TextBox(); - $object->setInnerMarginLeft($expected); - $this->assertEquals($expected, $object->getInnerMarginLeft()); - } - - /** - * Test set/get innerMarginLeft - */ - public function testSetGetInnerMargin() - { - $expected=5; - $object = new TextBox(); - $object->setInnerMargin($expected); - $this->assertEquals(array($expected, $expected, $expected, $expected), $object->getInnerMargin()); - } - - /** - * Test set/get borderSize - */ - public function testSetGetBorderSize() - { - $expected=2; - $object = new TextBox(); - $object->setBorderSize($expected); - $this->assertEquals($expected, $object->getBorderSize()); - } - - /** - * Test set/get borderColor - */ - public function testSetGetBorderColor() - { - $expected='red'; - $object = new TextBox(); - $object->setBorderColor($expected); - $this->assertEquals($expected, $object->getBorderColor()); - } -} diff --git a/tests/PhpWord/Tests/TemplateProcessorTest.php b/tests/PhpWord/Tests/TemplateProcessorTest.php deleted file mode 100644 index 04d1e77715..0000000000 --- a/tests/PhpWord/Tests/TemplateProcessorTest.php +++ /dev/null @@ -1,216 +0,0 @@ -load(__DIR__ . "/_files/xsl/remove_tables_by_needle.xsl"); - foreach (array('${employee.', '${scoreboard.') as $needle) { - $templateProcessor->applyXslStyleSheet($xslDOMDocument, array('needle' => $needle)); - } - - $documentFqfn = $templateProcessor->save(); - - $this->assertNotEmpty($documentFqfn, 'FQFN of the saved document is empty.'); - $this->assertFileExists($documentFqfn, "The saved document \"{$documentFqfn}\" doesn't exist."); - - $templateZip = new \ZipArchive(); - $templateZip->open($templateFqfn); - $templateXml = $templateZip->getFromName('word/document.xml'); - if ($templateZip->close() === false) { - throw new \Exception("Could not close zip file \"{$templateZip}\"."); - } - - $documentZip = new \ZipArchive(); - $documentZip->open($documentFqfn); - $documentXml = $documentZip->getFromName('word/document.xml'); - if ($documentZip->close() === false) { - throw new \Exception("Could not close zip file \"{$documentZip}\"."); - } - - $this->assertNotEquals($documentXml, $templateXml); - - return $documentFqfn; - } - - /** - * XSL stylesheet can be applied. - * - * @param string $actualDocumentFqfn - * @throws \Exception - * @covers ::applyXslStyleSheet - * @depends testTemplateCanBeSavedInTemporaryLocation - * @test - */ - final public function testXslStyleSheetCanBeApplied($actualDocumentFqfn) - { - $expectedDocumentFqfn = __DIR__ . "/_files/documents/without_table_macros.docx"; - - $actualDocumentZip = new \ZipArchive(); - $actualDocumentZip->open($actualDocumentFqfn); - $actualDocumentXml = $actualDocumentZip->getFromName('word/document.xml'); - if ($actualDocumentZip->close() === false) { - throw new \Exception("Could not close zip file \"{$actualDocumentFqfn}\"."); - } - - $expectedDocumentZip = new \ZipArchive(); - $expectedDocumentZip->open($expectedDocumentFqfn); - $expectedDocumentXml = $expectedDocumentZip->getFromName('word/document.xml'); - if ($expectedDocumentZip->close() === false) { - throw new \Exception("Could not close zip file \"{$expectedDocumentFqfn}\"."); - } - - $this->assertXmlStringEqualsXmlString($expectedDocumentXml, $actualDocumentXml); - } - - /** - * XSL stylesheet cannot be applied on failure in setting parameter value. - * - * @covers ::applyXslStyleSheet - * @expectedException \PhpOffice\PhpWord\Exception\Exception - * @expectedExceptionMessage Could not set values for the given XSL style sheet parameters. - * @test - */ - final public function testXslStyleSheetCanNotBeAppliedOnFailureOfSettingParameterValue() - { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); - - $xslDOMDocument = new \DOMDocument(); - $xslDOMDocument->load(__DIR__ . '/_files/xsl/passthrough.xsl'); - - /* - * We have to use error control below, because \XSLTProcessor::setParameter omits warning on failure. - * This warning fails the test. - */ - @$templateProcessor->applyXslStyleSheet($xslDOMDocument, array(1 => 'somevalue')); - } - - /** - * XSL stylesheet can be applied on failure of loading XML from template. - * - * @covers ::applyXslStyleSheet - * @expectedException \PhpOffice\PhpWord\Exception\Exception - * @expectedExceptionMessage Could not load XML from the given template. - * @test - */ - final public function testXslStyleSheetCanNotBeAppliedOnFailureOfLoadingXmlFromTemplate() - { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/corrupted_main_document_part.docx'); - - $xslDOMDocument = new \DOMDocument(); - $xslDOMDocument->load(__DIR__ . '/_files/xsl/passthrough.xsl'); - - /* - * We have to use error control below, because \DOMDocument::loadXML omits warning on failure. - * This warning fails the test. - */ - @$templateProcessor->applyXslStyleSheet($xslDOMDocument); - } - - /** - * @civers ::setValue - * @covers ::cloneRow - * @covers ::saveAs - * @test - */ - public function testCloneRow() - { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-merge.docx'); - - $this->assertEquals( - array('tableHeader', 'userId', 'userName', 'userLocation'), - $templateProcessor->getVariables() - ); - - $docName = 'clone-test-result.docx'; - $templateProcessor->setValue('tableHeader', utf8_decode('ééé')); - $templateProcessor->cloneRow('userId', 1); - $templateProcessor->setValue('userId#1', 'Test'); - $templateProcessor->saveAs($docName); - $docFound = file_exists($docName); - unlink($docName); - $this->assertTrue($docFound); - } - - /** - * @covers ::setValue - * @covers ::saveAs - * @test - */ - public function testVariablesCanBeReplacedInHeaderAndFooter() - { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); - - $this->assertEquals( - array('documentContent', 'headerValue', 'footerValue'), - $templateProcessor->getVariables() - ); - - $docName = 'header-footer-test-result.docx'; - $templateProcessor->setValue('headerValue', 'Header Value'); - $templateProcessor->setValue('documentContent', 'Document text.'); - $templateProcessor->setValue('footerValue', 'Footer Value'); - $templateProcessor->saveAs($docName); - $docFound = file_exists($docName); - unlink($docName); - $this->assertTrue($docFound); - } - - /** - * @covers ::cloneBlock - * @covers ::deleteBlock - * @covers ::saveAs - * @test - */ - public function testCloneDeleteBlock() - { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-delete-block.docx'); - - $this->assertEquals( - array('DELETEME', '/DELETEME', 'CLONEME', '/CLONEME'), - $templateProcessor->getVariables() - ); - - $docName = 'clone-delete-block-result.docx'; - $templateProcessor->cloneBlock('CLONEME', 3); - $templateProcessor->deleteBlock('DELETEME'); - $templateProcessor->saveAs($docName); - $docFound = file_exists($docName); - unlink($docName); - $this->assertTrue($docFound); - } -} diff --git a/tests/PhpWord/Tests/Writer/HTML/ElementTest.php b/tests/PhpWord/Tests/Writer/HTML/ElementTest.php deleted file mode 100644 index ae136d34d7..0000000000 --- a/tests/PhpWord/Tests/Writer/HTML/ElementTest.php +++ /dev/null @@ -1,56 +0,0 @@ -assertEquals('', $object->write()); - } - } - - /** - * Test write element text - */ - public function testWriteTextElement() - { - $object = new Text(new HTML(), new TextElement('A')); - $object->setOpeningText('-'); - $object->setClosingText('-'); - $object->setWithoutP(true); - - $this->assertEquals('-A-', $object->write()); - } -} diff --git a/tests/PhpWord/Tests/Writer/HTMLTest.php b/tests/PhpWord/Tests/Writer/HTMLTest.php deleted file mode 100644 index c3339d1a4f..0000000000 --- a/tests/PhpWord/Tests/Writer/HTMLTest.php +++ /dev/null @@ -1,120 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $object->getPhpWord()); - } - - /** - * Construct with null - * - * @expectedException \PhpOffice\PhpWord\Exception\Exception - * @expectedExceptionMessage No PhpWord assigned. - */ - public function testConstructWithNull() - { - $object = new HTML(); - $object->getPhpWord(); - } - - /** - * Save - */ - public function testSave() - { - $localImage = __DIR__ . "/../_files/images/PhpWord.png"; - $archiveImage = 'zip://' . __DIR__ . '/../_files/documents/reader.docx#word/media/image1.jpeg'; - $gdImage = 'http://php.net/images/logos/php-med-trans-light.gif'; - $objectSrc = __DIR__ . "/../_files/documents/sheet.xls"; - $file = __DIR__ . "/../_files/temp.html"; - - $phpWord = new PhpWord(); - - $docProps = $phpWord->getDocInfo(); - $docProps->setTitle('HTML Test'); - - $phpWord->addTitleStyle(1, array('bold' => true)); - $phpWord->addFontStyle('Font', array('name' => 'Verdana', 'size' => 11, - 'color' => 'FF0000', 'fgColor' => 'FF0000')); - $phpWord->addParagraphStyle('Paragraph', array('align' => 'center', 'spaceAfter' => 20, 'spaceBefore' => 20)); - $section = $phpWord->addSection(); - $section->addText('Test 1', 'Font', 'Paragraph'); - $section->addTextBreak(); - $section->addText('Test 2', array('name' => 'Tahoma', 'bold' => true, 'italic' => true, 'subscript' => true)); - $section->addLink('http://test.com/'); - $section->addTitle('Test', 1); - $section->addPageBreak(); - $section->addListItem('Test'); - $section->addImage($localImage); - $section->addImage($archiveImage); - $section->addImage($gdImage); - $section->addObject($objectSrc); - $section->addFootnote(); - $section->addEndnote(); - - $section = $phpWord->addSection(); - - $textrun = $section->addTextRun(array('align' => 'center')); - $textrun->addText('Test 3'); - $textrun->addTextBreak(); - - $textrun = $section->addTextRun('Paragraph'); - $textrun->addLink('http://test.com/'); - $textrun->addImage($localImage); - $textrun->addFootnote()->addText('Footnote'); - $textrun->addEndnote()->addText('Endnote'); - - $section = $phpWord->addSection(); - - $table = $section->addTable(); - $cell = $table->addRow()->addCell(); - $cell->addText('Test 1', array('superscript' => true, 'underline' => 'dash', 'strikethrough' => true)); - $cell->addTextRun(); - $cell->addLink('http://test.com/'); - $cell->addTextBreak(); - $cell->addListItem('Test'); - $cell->addImage($localImage); - $cell->addObject($objectSrc); - $cell->addFootnote(); - $cell->addEndnote(); - $cell = $table->addRow()->addCell(); - - $writer = new HTML($phpWord); - $writer->save($file); - - $this->assertTrue(file_exists($file)); - - unlink($file); - } -} diff --git a/tests/PhpWord/Tests/Writer/ODText/ElementTest.php b/tests/PhpWord/Tests/Writer/ODText/ElementTest.php deleted file mode 100644 index fa9532d147..0000000000 --- a/tests/PhpWord/Tests/Writer/ODText/ElementTest.php +++ /dev/null @@ -1,42 +0,0 @@ -write(); - - $this->assertEquals('', $xmlWriter->getData()); - } - } -} diff --git a/tests/PhpWord/Tests/Writer/ODText/Part/AbstractPartTest.php b/tests/PhpWord/Tests/Writer/ODText/Part/AbstractPartTest.php deleted file mode 100644 index 92fab26fdc..0000000000 --- a/tests/PhpWord/Tests/Writer/ODText/Part/AbstractPartTest.php +++ /dev/null @@ -1,58 +0,0 @@ -getMockForAbstractClass( - 'PhpOffice\\PhpWord\\Writer\\ODText\\Part\\AbstractPart' - ); - $object->setParentWriter(new ODText()); - $this->assertEquals( - new ODText(), - $object->getParentWriter() - ); - } - - /** - * covers ::getParentWriter - * @expectedException Exception - * @expectedExceptionMessage No parent WriterInterface assigned. - */ - public function testSetGetParentWriterNull() - { - $object = $this->getMockForAbstractClass( - 'PhpOffice\\PhpWord\\Writer\\ODText\\Part\\AbstractPart' - ); - $object->getParentWriter(); - } -} diff --git a/tests/PhpWord/Tests/Writer/PDF/MPDFTest.php b/tests/PhpWord/Tests/Writer/PDF/MPDFTest.php deleted file mode 100644 index 4728b7f148..0000000000 --- a/tests/PhpWord/Tests/Writer/PDF/MPDFTest.php +++ /dev/null @@ -1,51 +0,0 @@ -addSection(); - $section->addText('Test 1'); - - $rendererName = Settings::PDF_RENDERER_MPDF; - $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/mpdf/mpdf'); - Settings::setPdfRenderer($rendererName, $rendererLibraryPath); - $writer = new PDF($phpWord); - $writer->save($file); - - $this->assertTrue(file_exists($file)); - - unlink($file); - } -} diff --git a/tests/PhpWord/Tests/Writer/RTF/ElementTest.php b/tests/PhpWord/Tests/Writer/RTF/ElementTest.php deleted file mode 100644 index e090b34921..0000000000 --- a/tests/PhpWord/Tests/Writer/RTF/ElementTest.php +++ /dev/null @@ -1,41 +0,0 @@ -assertEquals('', $object->write()); - } - } -} diff --git a/tests/PhpWord/Tests/Writer/RTF/StyleTest.php b/tests/PhpWord/Tests/Writer/RTF/StyleTest.php deleted file mode 100644 index 542e34fe7c..0000000000 --- a/tests/PhpWord/Tests/Writer/RTF/StyleTest.php +++ /dev/null @@ -1,39 +0,0 @@ -assertEquals('', $object->write()); - } - } -} diff --git a/tests/PhpWord/Tests/Writer/RTFTest.php b/tests/PhpWord/Tests/Writer/RTFTest.php deleted file mode 100644 index 5b983b3578..0000000000 --- a/tests/PhpWord/Tests/Writer/RTFTest.php +++ /dev/null @@ -1,110 +0,0 @@ -assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $object->getPhpWord()); - } - - /** - * Construct with null - * - * @expectedException \PhpOffice\PhpWord\Exception\Exception - * @expectedExceptionMessage No PhpWord assigned. - */ - public function testConstructWithNull() - { - $object = new RTF(); - $object->getPhpWord(); - } - - /** - * Save - */ - public function testSave() - { - $imageSrc = __DIR__ . "/../_files/images/PhpWord.png"; - $objectSrc = __DIR__ . "/../_files/documents/sheet.xls"; - $file = __DIR__ . "/../_files/temp.rtf"; - - $phpWord = new PhpWord(); - $phpWord->addFontStyle('Font', array('name' => 'Verdana', 'size' => 11, - 'color' => 'FF0000', 'fgColor' => '00FF00')); - $phpWord->addParagraphStyle('Paragraph', array('align' => 'center')); - $section = $phpWord->addSection(); - $section->addText('Test 1', 'Font', 'Paragraph'); - $section->addTextBreak(); - $section->addText('Test 2', array('name' => 'Tahoma', 'bold' => true, 'italic' => true)); - $section->addLink('http://test.com/'); - $section->addTitle('Test', 1); - $section->addPageBreak(); - - // Rowspan - $table = $section->addTable(); - $table->addRow()->addCell(null, array('vMerge' => 'restart'))->addText('Test'); - $table->addRow()->addCell(null, array('vMerge' => 'continue'))->addText('Test'); - - // Nested table - $cell = $section->addTable()->addRow()->addCell(); - $cell->addTable()->addRow()->addCell(); - - $section->addListItem('Test'); - $section->addImage($imageSrc); - $section->addObject($objectSrc); - $section->addTOC(); - $section = $phpWord->addSection(); - $textrun = $section->addTextRun(); - $textrun->addText('Test 3'); - $textrun->addTextBreak(); - $writer = new RTF($phpWord); - $writer->save($file); - - $this->assertTrue(file_exists($file)); - - @unlink($file); - } - - /** - * Save - * - * @todo Haven't got any method to test this - */ - public function testSavePhpOutput() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - $section->addText('Test'); - $writer = new RTF($phpWord); - $writer->save('php://output'); - } -} diff --git a/tests/PhpWord/Tests/Writer/Word2007/ElementTest.php b/tests/PhpWord/Tests/Writer/Word2007/ElementTest.php deleted file mode 100644 index 92a74c1544..0000000000 --- a/tests/PhpWord/Tests/Writer/Word2007/ElementTest.php +++ /dev/null @@ -1,219 +0,0 @@ -write(); - - $this->assertEquals('', $xmlWriter->getData()); - } - } - - /** - * Test line element - */ - public function testLineElement() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - - $section->addLine(array('width' => 1000, 'height' => 1000, 'positioning' => 'absolute', 'flip' => true)); - $doc = TestHelperDOCX::getDocument($phpWord); - - $element = "/w:document/w:body/w:p/w:r/w:pict/v:shapetype"; - $this->assertTrue($doc->elementExists($element)); - } - - /** - * Test shape elements - */ - public function testShapeElements() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - - // Arc - $section->addShape( - 'arc', - array( - 'points' => '-90 20', - 'frame' => array('width' => 120, 'height' => 120), - 'outline' => array('color' => '#333333', 'weight' => 2, 'startArrow' => 'oval', 'endArrow' => 'open'), - ) - ); - - // Curve - $section->addShape( - 'curve', - array( - 'points' => '1,100 200,1 1,50 200,50', 'connector' => 'elbow', - 'outline' => array('color' => '#66cc00', 'weight' => 2, 'dash' => 'dash', - 'startArrow' => 'diamond', 'endArrow' => 'block'), - ) - ); - - // Line - $section->addShape( - 'line', - array( - 'points' => '1,1 150,30', - 'outline' => array('color' => '#cc00ff', 'line' => 'thickThin', 'weight' => 3, - 'startArrow' => 'oval', 'endArrow' => 'classic', 'endCap' => 'round'), - ) - ); - - // Polyline - $section->addShape( - 'polyline', - array( - 'points' => '1,30 20,10 55,20 75,10 100,40 115,50, 120,15 200,50', - 'outline' => array('color' => '#cc6666', 'weight' => 2, - 'startArrow' => 'none', 'endArrow' => 'classic'), - ) - ); - - // Rectangle - $section->addShape( - 'rect', - array( - 'roundness' => 0.2, - 'frame' => array('width' => 100, 'height' => 100, 'left' => 1, 'top' => 1), - 'fill' => array('color' => '#FFCC33'), - 'outline' => array('color' => '#990000', 'weight' => 1), - 'shadow' => array('color' => '#EEEEEE', 'offset' => '3pt,3pt'), - ) - ); - - // Oval - $section->addShape( - 'oval', - array( - 'frame' => array('width' => 100, 'height' => 70, 'left' => 1, 'top' => 1), - 'fill' => array('color' => '#33CC99'), - 'outline' => array('color' => '#333333', 'weight' => 2), - 'extrusion' => array('type' => 'perspective', 'color' => '#EEEEEE'), - ) - ); - - $doc = TestHelperDOCX::getDocument($phpWord); - - $elements = array('arc', 'curve', 'line', 'polyline', 'roundrect', 'oval'); - foreach ($elements as $element) { - $path = "/w:document/w:body/w:p/w:r/w:pict/v:{$element}"; - $this->assertTrue($doc->elementExists($path)); - } - } - - /** - * Test shape elements - */ - public function testChartElements() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - $style = array('width' => 1000000, 'height' => 1000000); - - $chartTypes = array('pie', 'doughnut', 'bar', 'line', 'area', 'scatter', 'radar'); - $categories = array('A', 'B', 'C', 'D', 'E'); - $series1 = array(1, 3, 2, 5, 4); - foreach ($chartTypes as $chartType) { - $section->addChart($chartType, $categories, $series1, $style); - } - $section->addChart('pie', $categories, $series1, array('3d' => true)); - - $doc = TestHelperDOCX::getDocument($phpWord); - - $index = 0; - foreach ($chartTypes as $chartType) { - $index++; - $file = "word/charts/chart{$index}.xml"; - $path = "/c:chartSpace/c:chart/c:plotArea/c:{$chartType}Chart"; - $this->assertTrue($doc->elementExists($path, $file)); - } - } - - /** - * Test form fields - */ - public function testFormFieldElements() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - - $section->addFormField('textinput')->setName('MyTextBox'); - $section->addFormField('checkbox')->setDefault(true)->setValue('Your name'); - $section->addFormField('dropdown')->setEntries(array('Choice 1', 'Choice 2', 'Choice 3')); - - $doc = TestHelperDOCX::getDocument($phpWord); - - $path = "/w:document/w:body/w:p/w:r/w:fldChar/w:ffData"; - $this->assertTrue($doc->elementExists($path . '/w:textInput')); - $this->assertTrue($doc->elementExists($path . '/w:checkBox')); - $this->assertTrue($doc->elementExists($path . '/w:ddList')); - } - - /** - * Test SDT elements - */ - public function testSDTElements() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - - $section->addSDT('comboBox'); - $section->addSDT('dropDownList'); - $section->addSDT('date'); - - $doc = TestHelperDOCX::getDocument($phpWord); - - $path = "/w:document/w:body/w:p/w:sdt/w:sdtPr"; - $this->assertTrue($doc->elementExists($path . '/w:comboBox')); - $this->assertTrue($doc->elementExists($path . '/w:dropDownList')); - $this->assertTrue($doc->elementExists($path . '/w:date')); - } -} diff --git a/tests/PhpWord/Tests/Writer/Word2007/Part/AbstractPartTest.php b/tests/PhpWord/Tests/Writer/Word2007/Part/AbstractPartTest.php deleted file mode 100644 index 2ff80ab83c..0000000000 --- a/tests/PhpWord/Tests/Writer/Word2007/Part/AbstractPartTest.php +++ /dev/null @@ -1,59 +0,0 @@ -getMockForAbstractClass( - 'PhpOffice\\PhpWord\\Writer\\Word2007\\Part\\AbstractPart' - ); - $object->setParentWriter(new Word2007()); - $this->assertEquals( - new Word2007(), - $object->getParentWriter() - ); - } - - /** - * covers ::getParentWriter - * @expectedException Exception - * @expectedExceptionMessage No parent WriterInterface assigned. - */ - public function testSetGetParentWriterNull() - { - $object = $this->getMockForAbstractClass( - 'PhpOffice\\PhpWord\\Writer\\Word2007\\Part\\AbstractPart' - ); - $object->getParentWriter(); - } -} diff --git a/tests/PhpWord/Tests/Writer/Word2007/Part/DocumentTest.php b/tests/PhpWord/Tests/Writer/Word2007/Part/DocumentTest.php deleted file mode 100644 index ef36e0dd72..0000000000 --- a/tests/PhpWord/Tests/Writer/Word2007/Part/DocumentTest.php +++ /dev/null @@ -1,554 +0,0 @@ -addSection(); - $section->addHeader(); - $section->addHeader('first'); - $style = $section->getStyle(); - $style->setLandscape(); - $style->setPageNumberingStart(2); - $style->setBorderSize(240); - $style->setBreakType('nextPage'); - - $doc = TestHelperDOCX::getDocument($phpWord); - $element = $doc->getElement('/w:document/w:body/w:sectPr/w:pgNumType'); - - $this->assertEquals(2, $element->getAttribute('w:start')); - } - - /** - * Write elements - */ - public function testElements() - { - $objectSrc = __DIR__ . "/../../../_files/documents/sheet.xls"; - - $phpWord = new PhpWord(); - $phpWord->addTitleStyle(1, array('color' => '333333', 'bold'=>true)); - $phpWord->addTitleStyle(2, array('color'=>'666666')); - $section = $phpWord->addSection(); - $section->addTOC(); - $section->addPageBreak(); - $section->addText('After page break.'); - $section->addTitle('Title 1', 1); - $section->addListItem('List Item 1', 0); - $section->addListItem('List Item 2', 0); - $section->addListItem('List Item 3', 0); - - $section = $phpWord->addSection(); - $section->addTitle('Title 2', 2); - $section->addObject($objectSrc); - $section->addTextBox(array()); - $section->addTextBox(array('wrappingStyle' => 'square', 'positioning' => 'relative', - 'posHorizontalRel' => 'margin', 'posVerticalRel' => 'margin', - 'innerMargin' => 10, 'borderSize' => 1, 'borderColor' => '#FF0')); - $section->addTextBox(array('wrappingStyle' => 'tight', 'positioning' => 'absolute', 'align' => 'center')); - $section->addListItemRun()->addText('List item run 1'); - $section->addField('DATE', array('dateformat'=>'dddd d MMMM yyyy H:mm:ss'), array('PreserveFormat', 'LunarCalendar')); - $section->addField('DATE', array('dateformat'=>'dddd d MMMM yyyy H:mm:ss'), array('PreserveFormat', 'SakaEraCalendar')); - $section->addField('DATE', array('dateformat'=>'dddd d MMMM yyyy H:mm:ss'), array('PreserveFormat', 'LastUsedFormat')); - $section->addField('PAGE', array('format'=>'ArabicDash')); - $section->addLine( - array( - 'width' => 10, - 'height' => 10, - 'positioning' => 'absolute', - 'beginArrow' => 'block', - 'endArrow' => 'open', - 'dash' => 'rounddot', - 'weight' => 10 - ) - ); - - $doc = TestHelperDOCX::getDocument($phpWord); - - // TOC - $element = $doc->getElement('/w:document/w:body/w:p[1]/w:pPr/w:tabs/w:tab'); - $this->assertEquals('right', $element->getAttribute('w:val')); - $this->assertEquals('dot', $element->getAttribute('w:leader')); - $this->assertEquals(9062, $element->getAttribute('w:pos')); - - // Page break - $element = $doc->getElement('/w:document/w:body/w:p[4]/w:r/w:br'); - $this->assertEquals('page', $element->getAttribute('w:type')); - - // Title - $element = $doc->getElement('/w:document/w:body/w:p[6]/w:pPr/w:pStyle'); - $this->assertEquals('Heading1', $element->getAttribute('w:val')); - - // List item - $element = $doc->getElement('/w:document/w:body/w:p[7]/w:pPr/w:numPr/w:numId'); - $this->assertEquals(3, $element->getAttribute('w:val')); - - // Object - $element = $doc->getElement('/w:document/w:body/w:p[12]/w:r/w:object/o:OLEObject'); - $this->assertEquals('Embed', $element->getAttribute('Type')); - } - - /** - * Write element with some styles - */ - public function testElementStyles() - { - $objectSrc = __DIR__ . "/../../../_files/documents/sheet.xls"; - - $tabs = array(new \PhpOffice\PhpWord\Style\Tab('right', 9090)); - $phpWord = new PhpWord(); - $phpWord->addParagraphStyle('pStyle', array( - 'align' => 'center', - 'tabs' => $tabs, - 'shading' => array('fill' => 'FFFF99'), - 'borderSize' => 4, - )); // Style #1 - $phpWord->addFontStyle('fStyle', array('size' => '20', 'bold' => true, 'allCaps' => true, - 'scale' => 200, 'spacing' => 240, 'kerning' => 10)); // Style #2 - $phpWord->addTitleStyle(1, array('color' => '333333', 'doubleStrikethrough' => true)); // Style #3 - $phpWord->addTableStyle('tStyle', array('borderSize' => 1)); - $fontStyle = new Font('text', array('align' => 'center')); - - $section = $phpWord->addSection(); - $section->addListItem('List Item', 0, null, null, 'pStyle'); // Style #5 - $section->addObject($objectSrc, array('align' => 'center')); - $section->addTOC($fontStyle); - $section->addTitle('Title 1', 1); - $section->addTOC('fStyle'); - $table = $section->addTable('tStyle'); - $table->setWidth(100); - $doc = TestHelperDOCX::getDocument($phpWord); - - // List item - $element = $doc->getElement('/w:document/w:body/w:p[1]/w:pPr/w:numPr/w:numId'); - $this->assertEquals(5, $element->getAttribute('w:val')); - - // Object - $element = $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:object/o:OLEObject'); - $this->assertEquals('Embed', $element->getAttribute('Type')); - - // TOC - $element = $doc->getElement('/w:document/w:body/w:p[3]/w:pPr/w:tabs/w:tab'); - $this->assertEquals('right', $element->getAttribute('w:val')); - $this->assertEquals('dot', $element->getAttribute('w:leader')); - $this->assertEquals(9062, $element->getAttribute('w:pos')); - } - - /** - * Test write text element - */ - public function testWriteText() - { - $rStyle = 'rStyle'; - $pStyle = 'pStyle'; - - $phpWord = new PhpWord(); - $phpWord->addFontStyle($rStyle, array('bold' => true)); - $phpWord->addParagraphStyle($pStyle, array('hanging' => 120, 'indent' => 120)); - $section = $phpWord->addSection(); - $section->addText('Test', $rStyle, $pStyle); - $doc = TestHelperDOCX::getDocument($phpWord); - - $element = "/w:document/w:body/w:p/w:r/w:rPr/w:rStyle"; - $this->assertEquals($rStyle, $doc->getElementAttribute($element, 'w:val')); - $element = "/w:document/w:body/w:p/w:pPr/w:pStyle"; - $this->assertEquals($pStyle, $doc->getElementAttribute($element, 'w:val')); - } - - /** - * Test write textrun element - */ - public function testWriteTextRun() - { - $pStyle = 'pStyle'; - $aStyle = array('align' => 'justify', 'spaceBefore' => 120, 'spaceAfter' => 120); - $imageSrc = __DIR__ . "/../../../_files/images/earth.jpg"; - - $phpWord = new PhpWord(); - $phpWord->addParagraphStyle($pStyle, $aStyle); - $section = $phpWord->addSection('Test'); - $textrun = $section->addTextRun($pStyle); - $textrun->addText('Test'); - $textrun->addTextBreak(); - $textrun = $section->addTextRun($aStyle); - $textrun->addLink('http://test.com/'); - $textrun->addImage($imageSrc, array('align' => 'center')); - $textrun->addFootnote(); - $doc = TestHelperDOCX::getDocument($phpWord); - - $parent = "/w:document/w:body/w:p"; - $this->assertTrue($doc->elementExists("{$parent}/w:pPr/w:pStyle[@w:val='{$pStyle}']")); - } - - /** - * Test write link element - */ - public function testWriteLink() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - $fontStyleArray = array('bold' => true); - $fontStyleName = 'Font Style'; - $paragraphStyleArray = array('align' => 'center'); - $paragraphStyleName = 'Paragraph Style'; - - $expected = 'PhpWord'; - $section->addLink('http://github.com/phpoffice/phpword', $expected); - $section->addLink('http://github.com/phpoffice/phpword', 'Test', $fontStyleArray, $paragraphStyleArray); - $section->addLink('http://github.com/phpoffice/phpword', 'Test', $fontStyleName, $paragraphStyleName); - - $doc = TestHelperDOCX::getDocument($phpWord); - $element = $doc->getElement('/w:document/w:body/w:p/w:hyperlink/w:r/w:t'); - - $this->assertEquals($expected, $element->nodeValue); - } - - /** - * Test write preserve text element - */ - public function testWritePreserveText() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - $footer = $section->addFooter(); - $fontStyleArray = array('bold' => true); - $fontStyleName = 'Font'; - $paragraphStyleArray = array('align' => 'right'); - $paragraphStyleName = 'Paragraph'; - - $footer->addPreserveText('Page {PAGE}'); - $footer->addPreserveText('{PAGE}', $fontStyleArray, $paragraphStyleArray); - $footer->addPreserveText('{PAGE}', $fontStyleName, $paragraphStyleName); - - $doc = TestHelperDOCX::getDocument($phpWord); - $preserve = $doc->getElement("w:p/w:r[2]/w:instrText", 'word/footer1.xml'); - - $this->assertEquals('PAGE', $preserve->nodeValue); - $this->assertEquals('preserve', $preserve->getAttribute('xml:space')); - } - - /** - * Test write text break - */ - public function testWriteTextBreak() - { - $fArray = array('size' => 12); - $pArray = array('spacing' => 240); - $fName = 'fStyle'; - $pName = 'pStyle'; - - $phpWord = new PhpWord(); - $phpWord->addFontStyle($fName, $fArray); - $phpWord->addParagraphStyle($pName, $pArray); - $section = $phpWord->addSection(); - $section->addTextBreak(); - $section->addTextBreak(1, $fArray, $pArray); - $section->addTextBreak(1, $fName, $pName); - $doc = TestHelperDOCX::getDocument($phpWord); - - $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:rPr/w:rStyle'); - $this->assertEquals($fName, $element->getAttribute('w:val')); - $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:pStyle'); - $this->assertEquals($pName, $element->getAttribute('w:val')); - } - - /** - * covers ::_writeImage - */ - public function testWriteImage() - { - $phpWord = new PhpWord(); - $styles = array('align' => 'left', 'width' => 40, 'height' => 40, 'marginTop' => -1, 'marginLeft' => -1); - $wraps = array('inline', 'behind', 'infront', 'square', 'tight'); - $section = $phpWord->addSection(); - foreach ($wraps as $wrap) { - $styles['wrappingStyle'] = $wrap; - $section->addImage(__DIR__ . "/../../../_files/images/earth.jpg", $styles); - } - - $archiveFile = realpath(__DIR__ . '/../../../_files/documents/reader.docx'); - $imageFile = 'word/media/image1.jpeg'; - $source = 'zip://' . $archiveFile . '#' . $imageFile; - $section->addImage($source); - - $doc = TestHelperDOCX::getDocument($phpWord); - - // behind - $element = $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:pict/v:shape'); - $style = $element->getAttribute('style'); - $this->assertRegExp('/z\-index:\-[0-9]*/', $style); - - // square - $element = $doc->getElement('/w:document/w:body/w:p[4]/w:r/w:pict/v:shape/w10:wrap'); - $this->assertEquals('square', $element->getAttribute('type')); - } - - /** - * covers ::_writeWatermark - */ - public function testWriteWatermark() - { - $imageSrc = __DIR__ . "/../../../_files/images/earth.jpg"; - - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - $header = $section->addHeader(); - $header->addWatermark($imageSrc); - $doc = TestHelperDOCX::getDocument($phpWord); - - $element = $doc->getElement("/w:document/w:body/w:sectPr/w:headerReference"); - $this->assertStringStartsWith("rId", $element->getAttribute('r:id')); - } - - /** - * covers ::_writeTitle - */ - public function testWriteTitle() - { - $phpWord = new PhpWord(); - $phpWord->addTitleStyle(1, array('bold' => true), array('spaceAfter' => 240)); - $phpWord->addSection()->addTitle('Test', 1); - $doc = TestHelperDOCX::getDocument($phpWord); - - $element = "/w:document/w:body/w:p/w:pPr/w:pStyle"; - $this->assertEquals('Heading1', $doc->getElementAttribute($element, 'w:val')); - } - - /** - * covers ::_writeCheckbox - */ - public function testWriteCheckbox() - { - $rStyle = 'rStyle'; - $pStyle = 'pStyle'; - - $phpWord = new PhpWord(); - // $phpWord->addFontStyle($rStyle, array('bold' => true)); - // $phpWord->addParagraphStyle($pStyle, array('hanging' => 120, 'indent' => 120)); - $section = $phpWord->addSection(); - $section->addCheckBox('Check1', 'Test', $rStyle, $pStyle); - $doc = TestHelperDOCX::getDocument($phpWord); - - $element = '/w:document/w:body/w:p/w:r/w:fldChar/w:ffData/w:name'; - $this->assertEquals('Check1', $doc->getElementAttribute($element, 'w:val')); - } - - /** - * covers ::_writeParagraphStyle - */ - public function testWriteParagraphStyle() - { - // Create the doc - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - $attributes = array( - 'align' => 'right', - 'widowControl' => false, - 'keepNext' => true, - 'keepLines' => true, - 'pageBreakBefore' => true, - ); - foreach ($attributes as $attribute => $value) { - $section->addText('Test', null, array($attribute => $value)); - } - $doc = TestHelperDOCX::getDocument($phpWord); - - // Test the attributes - $attributeCount = 0; - foreach ($attributes as $key => $value) { - $attributeCount++; - $nodeName = ($key == 'align') ? 'jc' : $key; - $path = "/w:document/w:body/w:p[{$attributeCount}]/w:pPr/w:{$nodeName}"; - if ($key != 'align') { - $value = $value ? 1 : 0; - } - $element = $doc->getElement($path); - $this->assertEquals($value, $element->getAttribute('w:val')); - } - } - - /** - * covers ::_writeTextStyle - */ - public function testWriteFontStyle() - { - $phpWord = new PhpWord(); - $styles['name'] = 'Verdana'; - $styles['size'] = 14; - $styles['bold'] = true; - $styles['italic'] = true; - $styles['underline'] = 'dash'; - $styles['strikethrough'] = true; - $styles['superScript'] = true; - $styles['color'] = 'FF0000'; - $styles['fgColor'] = 'yellow'; - $styles['bgColor'] = 'FFFF00'; - $styles['hint'] = 'eastAsia'; - $styles['smallCaps'] = true; - - $section = $phpWord->addSection(); - $section->addText('Test', $styles); - $doc = TestHelperDOCX::getDocument($phpWord); - - $parent = '/w:document/w:body/w:p/w:r/w:rPr'; - $this->assertEquals($styles['name'], $doc->getElementAttribute("{$parent}/w:rFonts", 'w:ascii')); - $this->assertEquals($styles['size'] * 2, $doc->getElementAttribute("{$parent}/w:sz", 'w:val')); - $this->assertTrue($doc->elementExists("{$parent}/w:b")); - $this->assertTrue($doc->elementExists("{$parent}/w:i")); - $this->assertEquals($styles['underline'], $doc->getElementAttribute("{$parent}/w:u", 'w:val')); - $this->assertTrue($doc->elementExists("{$parent}/w:strike")); - $this->assertEquals('superscript', $doc->getElementAttribute("{$parent}/w:vertAlign", 'w:val')); - $this->assertEquals($styles['color'], $doc->getElementAttribute("{$parent}/w:color", 'w:val')); - $this->assertEquals($styles['fgColor'], $doc->getElementAttribute("{$parent}/w:highlight", 'w:val')); - $this->assertTrue($doc->elementExists("{$parent}/w:smallCaps")); - } - - /** - * covers ::_writeTableStyle - */ - public function testWriteTableStyle() - { - $phpWord = new PhpWord(); - $rHeight = 120; - $cWidth = 120; - $imageSrc = __DIR__ . "/../../../_files/images/earth.jpg"; - $objectSrc = __DIR__ . "/../../../_files/documents/sheet.xls"; - - $tStyles["width"] = 50; - $tStyles["cellMarginTop"] = 120; - $tStyles["cellMarginRight"] = 120; - $tStyles["cellMarginBottom"] = 120; - $tStyles["cellMarginLeft"] = 120; - $rStyles["tblHeader"] = true; - $rStyles["cantSplit"] = true; - $cStyles["valign"] = 'top'; - $cStyles["textDirection"] = 'btLr'; - $cStyles["bgColor"] = 'FF0000'; - $cStyles["borderTopSize"] = 120; - $cStyles["borderBottomSize"] = 120; - $cStyles["borderLeftSize"] = 120; - $cStyles["borderRightSize"] = 120; - $cStyles["borderTopColor"] = 'FF0000'; - $cStyles["borderBottomColor"] = 'FF0000'; - $cStyles["borderLeftColor"] = 'FF0000'; - $cStyles["borderRightColor"] = 'FF0000'; - $cStyles["vMerge"] = 'restart'; - - $section = $phpWord->addSection(); - $table = $section->addTable($tStyles); - $table->setWidth = 100; - $table->addRow($rHeight, $rStyles); - $cell = $table->addCell($cWidth, $cStyles); - $cell->addText('Test'); - $cell->addTextBreak(); - $cell->addLink('http://google.com/'); - $cell->addListItem('Test'); - $cell->addImage($imageSrc); - $cell->addObject($objectSrc); - $textrun = $cell->addTextRun(); - $textrun->addText('Test'); - - $doc = TestHelperDOCX::getDocument($phpWord); - - $parent = '/w:document/w:body/w:tbl/w:tblPr/w:tblCellMar'; - // $this->assertEquals($tStyles['cellMarginTop'], $doc->getElementAttribute("{$parent}/w:top", 'w:w')); - // $this->assertEquals($tStyles['cellMarginRight'], $doc->getElementAttribute("{$parent}/w:right", 'w:w')); - // $this->assertEquals($tStyles['cellMarginBottom'], $doc->getElementAttribute("{$parent}/w:bottom", 'w:w')); - // $this->assertEquals($tStyles['cellMarginLeft'], $doc->getElementAttribute("{$parent}/w:right", 'w:w')); - - $parent = '/w:document/w:body/w:tbl/w:tr/w:trPr'; - $this->assertEquals($rHeight, $doc->getElementAttribute("{$parent}/w:trHeight", 'w:val')); - $this->assertEquals($rStyles['tblHeader'], $doc->getElementAttribute("{$parent}/w:tblHeader", 'w:val')); - $this->assertEquals($rStyles['cantSplit'], $doc->getElementAttribute("{$parent}/w:cantSplit", 'w:val')); - - $parent = '/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr'; - $this->assertEquals($cWidth, $doc->getElementAttribute("{$parent}/w:tcW", 'w:w')); - $this->assertEquals($cStyles['valign'], $doc->getElementAttribute("{$parent}/w:vAlign", 'w:val')); - $this->assertEquals($cStyles['textDirection'], $doc->getElementAttribute("{$parent}/w:textDirection", 'w:val')); - } - - /** - * covers ::_writeCellStyle - */ - public function testWriteCellStyleCellGridSpan() - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - - $table = $section->addTable(); - - $table->addRow(); - $cell = $table->addCell(200); - $cell->getStyle()->setGridSpan(5); - - $table->addRow(); - $table->addCell(40); - $table->addCell(40); - $table->addCell(40); - $table->addCell(40); - $table->addCell(40); - - $table->addRow(); - $cell = $table->addCell(200, array('borderRightColor' => 'FF0000')); - $cell->getStyle()->setGridSpan(5); - - $doc = TestHelperDOCX::getDocument($phpWord); - $element = $doc->getElement('/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:gridSpan'); - - $this->assertEquals(5, $element->getAttribute('w:val')); - } - - /** - * Test write gutter and line numbering - */ - public function testWriteGutterAndLineNumbering() - { - $pageMarginPath = '/w:document/w:body/w:sectPr/w:pgMar'; - $lineNumberingPath = '/w:document/w:body/w:sectPr/w:lnNumType'; - - $phpWord = new PhpWord(); - $phpWord->addSection(array('gutter' => 240, 'lineNumbering' => array())); - $doc = TestHelperDOCX::getDocument($phpWord); - - $this->assertEquals(240, $doc->getElement($pageMarginPath)->getAttribute('w:gutter')); - $this->assertTrue($doc->elementExists($lineNumberingPath)); - } -} diff --git a/tests/PhpWord/Tests/Writer/Word2007/Part/SettingsTest.php b/tests/PhpWord/Tests/Writer/Word2007/Part/SettingsTest.php deleted file mode 100644 index 7d4d1849c6..0000000000 --- a/tests/PhpWord/Tests/Writer/Word2007/Part/SettingsTest.php +++ /dev/null @@ -1,68 +0,0 @@ -getProtection()->setEditing('forms'); - - $doc = TestHelperDOCX::getDocument($phpWord); - - $file = 'word/settings.xml'; - - $path = '/w:settings/w:documentProtection'; - $this->assertTrue($doc->elementExists($path, $file)); - } - - /** - * Test compatibility - */ - public function testCompatibility() - { - $phpWord = new PhpWord(); - $phpWord->getCompatibility()->setOoxmlVersion(15); - - $doc = TestHelperDOCX::getDocument($phpWord); - - $file = 'word/settings.xml'; - - $path = '/w:settings/w:compat/w:compatSetting'; - $this->assertTrue($doc->elementExists($path, $file)); - } -} diff --git a/tests/PhpWord/Tests/Writer/Word2007/Part/StylesTest.php b/tests/PhpWord/Tests/Writer/Word2007/Part/StylesTest.php deleted file mode 100644 index 103caa81b5..0000000000 --- a/tests/PhpWord/Tests/Writer/Word2007/Part/StylesTest.php +++ /dev/null @@ -1,85 +0,0 @@ - 'both'); - $pBase = array('basedOn' => 'Normal'); - $pNew = array('basedOn' => 'Base Style', 'next' => 'Normal'); - $rStyle = array('size' => 20); - $tStyle = array( - 'bgColor' => 'FF0000', - 'cellMargin' => 120, - 'borderSize' => 120, - ); - $firstRowStyle = array( - 'bgColor' => '0000FF', - 'borderSize' => 120, - 'borderColor' => '00FF00', - ); - $phpWord->setDefaultParagraphStyle($pStyle); - $phpWord->addParagraphStyle('Base Style', $pBase); - $phpWord->addParagraphStyle('New Style', $pNew); - $phpWord->addFontStyle('New Style', $rStyle, $pStyle); - $phpWord->addTableStyle('Table Style', $tStyle, $firstRowStyle); - $phpWord->addTitleStyle(1, $rStyle, $pStyle); - $doc = TestHelperDOCX::getDocument($phpWord); - - $file = 'word/styles.xml'; - - // Normal style generated? - $path = '/w:styles/w:style[@w:styleId="Normal"]/w:name'; - $element = $doc->getElement($path, $file); - $this->assertEquals('Normal', $element->getAttribute('w:val')); - - // Parent style referenced? - $path = '/w:styles/w:style[@w:styleId="New Style"]/w:basedOn'; - $element = $doc->getElement($path, $file); - $this->assertEquals('Base Style', $element->getAttribute('w:val')); - - // Next paragraph style correct? - $path = '/w:styles/w:style[@w:styleId="New Style"]/w:next'; - $element = $doc->getElement($path, $file); - $this->assertEquals('Normal', $element->getAttribute('w:val')); - } -} diff --git a/tests/PhpWord/Tests/Writer/Word2007/Style/FontTest.php b/tests/PhpWord/Tests/Writer/Word2007/Style/FontTest.php deleted file mode 100644 index af11c054ff..0000000000 --- a/tests/PhpWord/Tests/Writer/Word2007/Style/FontTest.php +++ /dev/null @@ -1,54 +0,0 @@ -addSection(); - $textrun = $section->addTextRun(); - $textrun->addText('سلام این یک پاراگراف راست به چپ است', array('rtl' => true)); - $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); - - $file = 'word/document.xml'; - $path = '/w:document/w:body/w:p/w:r/w:rPr/w:rtl'; - $this->assertTrue($doc->elementExists($path, $file)); - } -} diff --git a/tests/PhpWord/Tests/Writer/Word2007/StyleTest.php b/tests/PhpWord/Tests/Writer/Word2007/StyleTest.php deleted file mode 100644 index 8dd229c311..0000000000 --- a/tests/PhpWord/Tests/Writer/Word2007/StyleTest.php +++ /dev/null @@ -1,65 +0,0 @@ -write(); - - $this->assertEquals('', $xmlWriter->getData()); - } - } - - /** - * Test method exceptions - */ - public function testMethodExceptions() - { - $styles = array( - 'Frame' => 'writeAlignment', - 'Line' => 'writeStroke', - 'TextBox' => 'writeBorder', - ); - foreach ($styles as $style => $method) { - $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Style\\' . $style; - $xmlWriter = new XMLWriter(); - $object = new $objectClass($xmlWriter); - $object->$method(); - - $this->assertEquals('', $xmlWriter->getData()); - } - } -} diff --git a/tests/PhpWord/Tests/_files/documents/reader.docx b/tests/PhpWord/Tests/_files/documents/reader.docx deleted file mode 100644 index d09091b119..0000000000 Binary files a/tests/PhpWord/Tests/_files/documents/reader.docx and /dev/null differ diff --git a/tests/PhpWord/Tests/_files/documents/without_table_macros.docx b/tests/PhpWord/Tests/_files/documents/without_table_macros.docx deleted file mode 100644 index e4e9767fc1..0000000000 Binary files a/tests/PhpWord/Tests/_files/documents/without_table_macros.docx and /dev/null differ diff --git a/tests/PhpWord/Tests/_files/templates/clone-delete-block.docx b/tests/PhpWord/Tests/_files/templates/clone-delete-block.docx deleted file mode 100644 index 049d5ca415..0000000000 Binary files a/tests/PhpWord/Tests/_files/templates/clone-delete-block.docx and /dev/null differ diff --git a/tests/PhpWord/Tests/_files/templates/header-footer.docx b/tests/PhpWord/Tests/_files/templates/header-footer.docx deleted file mode 100644 index 647d52222d..0000000000 Binary files a/tests/PhpWord/Tests/_files/templates/header-footer.docx and /dev/null differ diff --git a/tests/PhpWord/Tests/_files/templates/with_table_macros.docx b/tests/PhpWord/Tests/_files/templates/with_table_macros.docx deleted file mode 100644 index cd5ed6cedb..0000000000 Binary files a/tests/PhpWord/Tests/_files/templates/with_table_macros.docx and /dev/null differ diff --git a/tests/PhpWord/Tests/_includes/XmlDocument.php b/tests/PhpWord/Tests/_includes/XmlDocument.php deleted file mode 100644 index 2e161bb1a5..0000000000 --- a/tests/PhpWord/Tests/_includes/XmlDocument.php +++ /dev/null @@ -1,164 +0,0 @@ -path = realpath($path); - } - - /** - * Get DOM from file - * - * @param string $file - * @return \DOMDocument - */ - public function getFileDom($file = 'word/document.xml') - { - if (null !== $this->dom && $file === $this->file) { - return $this->dom; - } - - $this->xpath = null; - $this->file = $file; - - $file = $this->path . '/' . $file; - $this->dom = new \DOMDocument(); - $this->dom->load($file); - return $this->dom; - } - - /** - * Get node list - * - * @param string $path - * @param string $file - * @return \DOMNodeList - */ - public function getNodeList($path, $file = 'word/document.xml') - { - if ($this->dom === null || $file !== $this->file) { - $this->getFileDom($file); - } - - if (null === $this->xpath) { - $this->xpath = new \DOMXpath($this->dom); - - } - - return $this->xpath->query($path); - } - - /** - * Get element - * - * @param string $path - * @param string $file - * @return \DOMElement - */ - public function getElement($path, $file = 'word/document.xml') - { - $elements = $this->getNodeList($path, $file); - - return $elements->item(0); - } - - /** - * Get file name - * - * @return string - */ - public function getFile() - { - return $this->file; - } - - /** - * Get path - * - * @return string - */ - public function getPath() - { - return $this->path; - } - - /** - * Get element attribute - * - * @param string $path - * @param string $attribute - * @param string $file - * @return string - */ - public function getElementAttribute($path, $attribute, $file = 'word/document.xml') - { - return $this->getElement($path, $file)->getAttribute($attribute); - } - - /** - * Check if element exists - * - * @param string $path - * @param string $file - * @return string - */ - public function elementExists($path, $file = 'word/document.xml') - { - $nodeList = $this->getNodeList($path, $file); - return !($nodeList->length == 0); - } -} diff --git a/tests/PhpWordTests/AbstractTestReader.php b/tests/PhpWordTests/AbstractTestReader.php new file mode 100644 index 0000000000..a51af6b96a --- /dev/null +++ b/tests/PhpWordTests/AbstractTestReader.php @@ -0,0 +1,66 @@ + ['class' => 'PhpOffice\PhpWord\Reader\Word2007\Styles', 'xml' => '{toReplace}'], + 'document' => ['class' => 'PhpOffice\PhpWord\Reader\Word2007\Document', 'xml' => '{toReplace}'], + 'footnotes' => ['class' => 'PhpOffice\PhpWord\Reader\Word2007\Footnotes', 'xml' => '{toReplace}'], + 'endnotes' => ['class' => 'PhpOffice\PhpWord\Reader\Word2007\Endnotes', 'xml' => '{toReplace}'], + 'settings' => ['class' => 'PhpOffice\PhpWord\Reader\Word2007\Settings', 'xml' => '{toReplace}'], + ]; + + /** + * Builds a PhpWord instance based on the xml passed. + * + * @return PhpWord + */ + protected function getDocumentFromString(array $partXmls = []) + { + $file = __DIR__ . '/_files/temp.docx'; + $zip = new ZipArchive(); + $zip->open($file, ZipArchive::CREATE | ZipArchive::OVERWRITE); + foreach ($this->parts as $partName => $part) { + if (array_key_exists($partName, $partXmls)) { + $zip->addFromString("{$partName}.xml", str_replace('{toReplace}', $partXmls[$partName], $this->parts[$partName]['xml'])); + } + } + $zip->close(); + + $phpWord = new PhpWord(); + foreach ($this->parts as $partName => $part) { + if (array_key_exists($partName, $partXmls)) { + $className = $this->parts[$partName]['class']; + $reader = new $className($file, "{$partName}.xml"); + $reader->read($phpWord); + } + } + unlink($file); + + return $phpWord; + } +} diff --git a/tests/PhpWordTests/AbstractWebServerEmbedded.php b/tests/PhpWordTests/AbstractWebServerEmbedded.php new file mode 100644 index 0000000000..fde7e1007c --- /dev/null +++ b/tests/PhpWordTests/AbstractWebServerEmbedded.php @@ -0,0 +1,83 @@ +start(); + while (!self::$httpServer->isRunning()) { + usleep(1000); + } + } + + public static function tearDownAfterClass(): void + { + self::$httpServer->stop(); + } + + protected static function getBaseUrl() + { + return 'http://localhost:8080/'; + } + + protected static function getRemoteImageUrl() + { + if (self::$httpServer) { + return self::getBaseUrl() . '/images/new-php-logo.png'; + } + + return 'http://php.net/images/logos/new-php-logo.png'; + } + + protected static function 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) { + return self::getBaseUrl() . '/images/mario.gif'; + } + + return 'http://php.net/images/logos/php-med-trans-light.gif'; + } + + protected static function getRemoteBmpImageUrl() + { + if (self::$httpServer) { + return self::getBaseUrl() . '/images/duke_nukem.bmp'; + } + + return 'https://samples.libav.org/image-samples/RACECAR.BMP'; + } +} diff --git a/tests/PhpWordTests/AutoloaderTest.php b/tests/PhpWordTests/AutoloaderTest.php new file mode 100644 index 0000000000..076bc8ebca --- /dev/null +++ b/tests/PhpWordTests/AutoloaderTest.php @@ -0,0 +1,56 @@ +addItem(new Footnote()); // addItem #1 + + self::assertEquals(1, $object->addItem(new Footnote())); // addItem #2. Should returns new item index + self::assertCount(2, $object->getItems()); // getItems returns array + 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/ComplexType/FootnotePropertiesTest.php b/tests/PhpWordTests/ComplexType/FootnotePropertiesTest.php new file mode 100644 index 0000000000..6472968d3b --- /dev/null +++ b/tests/PhpWordTests/ComplexType/FootnotePropertiesTest.php @@ -0,0 +1,80 @@ +setPos(FootnoteProperties::POSITION_DOC_END); + $footnoteProp->setNumFmt(NumberFormat::LOWER_ROMAN); + $footnoteProp->setNumStart(2); + $footnoteProp->setNumRestart(FootnoteProperties::RESTART_NUMBER_EACH_PAGE); + + self::assertEquals(FootnoteProperties::POSITION_DOC_END, $footnoteProp->getPos()); + self::assertEquals(NumberFormat::LOWER_ROMAN, $footnoteProp->getNumFmt()); + self::assertEquals(2, $footnoteProp->getNumStart()); + self::assertEquals(FootnoteProperties::RESTART_NUMBER_EACH_PAGE, $footnoteProp->getNumRestart()); + } + + /** + * Test throws exception if wrong position given. + */ + public function testWrongPos(): void + { + $this->expectException(InvalidArgumentException::class); + $footnoteProp = new FootnoteProperties(); + $footnoteProp->setPos(NumberFormat::LOWER_ROMAN); + } + + /** + * Test throws exception if wrong number format given. + */ + public function testWrongNumFmt(): void + { + $this->expectException(InvalidArgumentException::class); + $footnoteProp = new FootnoteProperties(); + $footnoteProp->setNumFmt(FootnoteProperties::POSITION_DOC_END); + } + + /** + * Test throws exception if wrong number restart given. + */ + public function testWrongNumRestart(): void + { + $this->expectException(InvalidArgumentException::class); + $footnoteProp = new FootnoteProperties(); + $footnoteProp->setNumRestart(NumberFormat::LOWER_ROMAN); + } +} diff --git a/tests/PhpWordTests/ComplexType/ProofStateTest.php b/tests/PhpWordTests/ComplexType/ProofStateTest.php new file mode 100644 index 0000000000..700e3f6d98 --- /dev/null +++ b/tests/PhpWordTests/ComplexType/ProofStateTest.php @@ -0,0 +1,63 @@ +setGrammar(ProofState::CLEAN); + $pState->setSpelling(ProofState::DIRTY); + + self::assertEquals(ProofState::CLEAN, $pState->getGrammar()); + self::assertEquals(ProofState::DIRTY, $pState->getSpelling()); + } + + /** + * Test throws exception if wrong grammar proof state value given. + */ + public function testWrongGrammar(): void + { + $this->expectException(InvalidArgumentException::class); + $pState = new ProofState(); + $pState->setGrammar('Wrong'); + } + + /** + * Test throws exception if wrong spelling proof state value given. + */ + public function testWrongSpelling(): void + { + $this->expectException(InvalidArgumentException::class); + $pState = new ProofState(); + $pState->setSpelling('Wrong'); + } +} diff --git a/tests/PhpWordTests/ComplexType/RubyPropertiesTest.php b/tests/PhpWordTests/ComplexType/RubyPropertiesTest.php new file mode 100644 index 0000000000..62bc0b0739 --- /dev/null +++ b/tests/PhpWordTests/ComplexType/RubyPropertiesTest.php @@ -0,0 +1,140 @@ +getAlignment()); + self::assertNotEmpty($properties->getAlignment()); + self::assertIsFloat($properties->getFontFaceSize()); + self::assertIsFloat($properties->getFontPointsAboveBaseText()); + self::assertIsFloat($properties->getFontSizeForBaseText()); + self::assertIsString($properties->getLanguageId()); + self::assertTrue($properties->getLanguageId() !== ''); + } + + /** + * Get/set alignment. + */ + public function testAlignment(): void + { + $properties = new RubyProperties(); + self::assertIsString($properties->getAlignment()); + self::assertNotEmpty($properties->getAlignment()); + $properties->setAlignment(RubyProperties::ALIGNMENT_RIGHT_VERTICAL); + self::assertEquals(RubyProperties::ALIGNMENT_RIGHT_VERTICAL, $properties->getAlignment()); + } + + /** + * Set valid alignments. Make sure we can set all valid types - should not throw exception. + */ + public function testValidAlignments(): void + { + $properties = new RubyProperties(); + $types = [ + RubyProperties::ALIGNMENT_CENTER, + RubyProperties::ALIGNMENT_DISTRIBUTE_LETTER, + RubyProperties::ALIGNMENT_DISTRIBUTE_SPACE, + RubyProperties::ALIGNMENT_LEFT, + RubyProperties::ALIGNMENT_RIGHT, + RubyProperties::ALIGNMENT_RIGHT_VERTICAL, + ]; + foreach ($types as $type) { + $properties->setAlignment($type); + self::assertEquals($type, $properties->getAlignment()); + } + } + + /** + * Test throws exception on invalid alignment type. + */ + public function testInvalidAlignment(): void + { + $this->expectException(InvalidArgumentException::class); + $properties = new RubyProperties(); + $properties->setAlignment('invalid alignment type'); + } + + /** + * Get/set font face size. + */ + public function testFontFaceSize(): void + { + $properties = new RubyProperties(); + + self::assertTrue($properties->getFontFaceSize() > 0); + $properties->setFontFaceSize(42.42); + self::assertEqualsWithDelta(42.42, $properties->getFontFaceSize(), 0.00001); // use delta as it is a float compare + self::assertIsFloat($properties->getFontFaceSize()); + } + + /** + * Get/set font points above base text. + */ + public function testFontPointsAboveBaseText(): void + { + $properties = new RubyProperties(); + + self::assertTrue($properties->getFontPointsAboveBaseText() > 0); + $properties->setFontPointsAboveBaseText(43.42); + self::assertEqualsWithDelta(43.42, $properties->getFontPointsAboveBaseText(), 0.00001); // use delta as it is a float compare + self::assertIsFloat($properties->getFontPointsAboveBaseText()); + } + + /** + * Get/set font size for base text. + */ + public function testFontSizeForBaseText(): void + { + $properties = new RubyProperties(); + + self::assertTrue($properties->getFontSizeForBaseText() > 0); + $properties->setFontSizeForBaseText(45.42); + self::assertEqualsWithDelta(45.42, $properties->getFontSizeForBaseText(), 0.00001); // use delta as it is a float compare + self::assertIsFloat($properties->getFontSizeForBaseText()); + } + + /** + * Get/set language id. + */ + public function testLanguageId(): void + { + $properties = new RubyProperties(); + + self::assertNotEmpty($properties->getLanguageId()); + $properties->setLanguageId('en-US'); + self::assertIsString($properties->getLanguageId()); + self::assertEquals('en-US', $properties->getLanguageId()); + } +} diff --git a/tests/PhpWordTests/Element/AbstractElementTest.php b/tests/PhpWordTests/Element/AbstractElementTest.php new file mode 100644 index 0000000000..9ce05750b8 --- /dev/null +++ b/tests/PhpWordTests/Element/AbstractElementTest.php @@ -0,0 +1,62 @@ +getMockForAbstractClass(AbstractElement::class); + } else { + /** @var AbstractElement $stub */ + $stub = new class() extends AbstractElement { + }; + } + $ival = mt_rand(0, 100); + $stub->setElementIndex($ival); + self::assertEquals($ival, $stub->getElementIndex()); + } + + /** + * Test set/get element unique Id. + */ + public function testElementId(): void + { + // @phpstan-ignore-next-line + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(AbstractElement::class); + } else { + /** @var AbstractElement $stub */ + $stub = new class() extends AbstractElement { + }; + } + $stub->setElementId(); + self::assertEquals(6, strlen($stub->getElementId())); + } +} diff --git a/tests/PhpWord/Tests/Element/PageBreakTest.php b/tests/PhpWordTests/Element/BookmarkTest.php similarity index 53% rename from tests/PhpWord/Tests/Element/PageBreakTest.php rename to tests/PhpWordTests/Element/BookmarkTest.php index 6b038a8257..097166736f 100644 --- a/tests/PhpWord/Tests/Element/PageBreakTest.php +++ b/tests/PhpWordTests/Element/BookmarkTest.php @@ -1,4 +1,5 @@ assertInstanceOf('PhpOffice\\PhpWord\\Element\\PageBreak', $oPageBreak); + self::assertEquals($bookmarkName, $oBookmark->getName()); } } diff --git a/tests/PhpWordTests/Element/CellTest.php b/tests/PhpWordTests/Element/CellTest.php new file mode 100644 index 0000000000..2fedcafc24 --- /dev/null +++ b/tests/PhpWordTests/Element/CellTest.php @@ -0,0 +1,273 @@ +getWidth()); + } + + /** + * New instance with array. + */ + public function testConstructWithStyleArray(): void + { + $oCell = new Cell(null, ['valign' => 'center']); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Cell', $oCell->getStyle()); + self::assertNull($oCell->getWidth()); + } + + /** + * Add text. + */ + public function testAddText(): void + { + $oCell = new Cell(); + $element = $oCell->addText('text'); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + } + + /** + * Add non-UTF8. + */ + public function testAddTextNotUTF8(): void + { + $oCell = new Cell(); + $element = $oCell->addText(utf8decode('ééé')); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + self::assertEquals('ééé', $element->getText()); + } + + /** + * Add link. + */ + public function testAddLink(): void + { + $oCell = new Cell(); + $element = $oCell->addLink(utf8decode('ééé'), utf8decode('ééé')); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); + } + + /** + * Add text break. + */ + public function testAddTextBreak(): void + { + $oCell = new Cell(); + $oCell->addTextBreak(); + + self::assertCount(1, $oCell->getElements()); + } + + /** + * Add list item. + */ + public function testAddListItem(): void + { + $oCell = new Cell(); + $element = $oCell->addListItem('text'); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\ListItem', $element); + self::assertEquals('text', $element->getTextObject()->getText()); + } + + /** + * Add list item non-UTF8. + */ + public function testAddListItemNotUTF8(): void + { + $oCell = new Cell(); + $element = $oCell->addListItem(utf8decode('ééé')); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\ListItem', $element); + self::assertEquals('ééé', $element->getTextObject()->getText()); + } + + /** + * Add image section. + */ + public function testAddImageSection(): void + { + $src = __DIR__ . '/../_files/images/earth.jpg'; + $oCell = new Cell(); + $element = $oCell->addImage($src); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); + } + + /** + * Add image header. + */ + public function testAddImageHeader(): void + { + $src = __DIR__ . '/../_files/images/earth.jpg'; + $oCell = new Cell('header', 1); + $element = $oCell->addImage($src); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); + } + + /** + * Add image footer. + */ + public function testAddImageFooter(): void + { + $src = __DIR__ . '/../_files/images/earth.jpg'; + $oCell = new Cell('footer', 1); + $element = $oCell->addImage($src); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); + } + + /** + * Add image section by URL. + */ + public function testAddImageSectionByUrl(): void + { + $oCell = new Cell(); + $element = $oCell->addImage(self::getRemoteGifImageUrl()); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); + } + + /** + * Add object. + */ + public function testAddObjectXLS(): void + { + $src = __DIR__ . '/../_files/documents/sheet.xls'; + $oCell = new Cell(); + $element = $oCell->addObject($src); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\OLEObject', $element); + } + + /** + * Test add object exception. + */ + public function testAddObjectException(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\InvalidObjectException::class); + $src = __DIR__ . '/../_files/xsl/passthrough.xsl'; + $oCell = new Cell(); + $oCell->addObject($src); + } + + /** + * Add preserve text. + */ + public function testAddPreserveText(): void + { + $oCell = new Cell(); + $oCell->setDocPart('Header', 1); + $element = $oCell->addPreserveText('text'); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); + } + + /** + * Add preserve text non-UTF8. + */ + public function testAddPreserveTextNotUTF8(): void + { + $oCell = new Cell(); + $oCell->setDocPart('Header', 1); + $element = $oCell->addPreserveText(utf8decode('ééé')); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); + self::assertEquals(['ééé'], $element->getText()); + } + + /** + * Add preserve text exception. + */ + public function testAddPreserveTextException(): void + { + $this->expectException(BadMethodCallException::class); + $oCell = new Cell(); + $oCell->setDocPart('TextRun', 1); + $oCell->addPreserveText('text'); + } + + /** + * Add text run. + */ + public function testCreateTextRun(): void + { + $oCell = new Cell(); + $element = $oCell->addTextRun(); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $element); + } + + /** + * Add check box. + */ + public function testAddCheckBox(): void + { + $oCell = new Cell(); + $element = $oCell->addCheckBox(utf8decode('ééé'), utf8decode('ééé')); + + self::assertCount(1, $oCell->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\CheckBox', $element); + } + + /** + * Get elements. + */ + public function testGetElements(): void + { + $oCell = new Cell(); + + self::assertIsArray($oCell->getElements()); + } +} diff --git a/tests/PhpWordTests/Element/CheckBoxTest.php b/tests/PhpWordTests/Element/CheckBoxTest.php new file mode 100644 index 0000000000..fbdbf36aa3 --- /dev/null +++ b/tests/PhpWordTests/Element/CheckBoxTest.php @@ -0,0 +1,88 @@ +getText()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oCheckBox->getFontStyle()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oCheckBox->getParagraphStyle()); + } + + /** + * Get name and text. + */ + public function testCheckBox(): void + { + $oCheckBox = new CheckBox('chkBox', 'CheckBox'); + + self::assertEquals('chkBox', $oCheckBox->getName()); + self::assertEquals('CheckBox', $oCheckBox->getText()); + } + + /** + * Get font style. + */ + public function testFont(): void + { + $oCheckBox = new CheckBox('chkBox', 'CheckBox', 'fontStyle'); + self::assertEquals('fontStyle', $oCheckBox->getFontStyle()); + + $oCheckBox->setFontStyle(['bold' => true, 'italic' => true, 'size' => 16]); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oCheckBox->getFontStyle()); + } + + /** + * Font style as object. + */ + public function testFontObject(): void + { + $font = new Font(); + $oCheckBox = new CheckBox('chkBox', 'CheckBox', $font); + self::assertEquals($font, $oCheckBox->getFontStyle()); + } + + /** + * Get paragraph style. + */ + public function testParagraph(): void + { + $oCheckBox = new CheckBox('chkBox', 'CheckBox', 'fontStyle', 'paragraphStyle'); + self::assertEquals('paragraphStyle', $oCheckBox->getParagraphStyle()); + + $oCheckBox->setParagraphStyle(['alignment' => Jc::CENTER, 'spaceAfter' => 100]); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oCheckBox->getParagraphStyle()); + } +} diff --git a/tests/PhpWordTests/Element/CommentTest.php b/tests/PhpWordTests/Element/CommentTest.php new file mode 100644 index 0000000000..f76316d890 --- /dev/null +++ b/tests/PhpWordTests/Element/CommentTest.php @@ -0,0 +1,138 @@ +setStartElement($oText); + $oComment->setEndElement($oText); + + self::assertEquals($author, $oComment->getAuthor()); + self::assertEquals($date, $oComment->getDate()); + self::assertEquals($initials, $oComment->getInitials()); + self::assertEquals($oText, $oComment->getStartElement()); + 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. + */ + public function testAddText(): void + { + $oComment = new Comment('Test User', new DateTime(), 'my_initials'); + $element = $oComment->addText('text'); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + self::assertCount(1, $oComment->getElements()); + self::assertEquals('text', $element->getText()); + } + + /** + * Get elements. + */ + public function testGetElements(): void + { + $oComment = new Comment('Test User', new DateTime(), 'my_initials'); + + self::assertIsArray($oComment->getElements()); + } + + /** + * Set/get relation Id. + */ + public function testRelationId(): void + { + $oComment = new Comment('Test User', new DateTime(), 'my_initials'); + + $iVal = mt_rand(1, 1000); + $oComment->setRelationId($iVal); + self::assertEquals($iVal, $oComment->getRelationId()); + } + + public function testExceptionOnCommentStartOnComment(): void + { + $this->expectException(InvalidArgumentException::class); + $dummyComment = new Comment('Test User', new DateTime(), 'my_initials'); + $oComment = new Comment('Test User', new DateTime(), 'my_initials'); + $oComment->setCommentRangeStart($dummyComment); + } + + public function testExceptionOnCommentEndOnComment(): void + { + $this->expectException(InvalidArgumentException::class); + $dummyComment = new Comment('Test User', new DateTime(), 'my_initials'); + $oComment = new Comment('Test User', new DateTime(), 'my_initials'); + $oComment->setCommentRangeEnd($dummyComment); + } +} diff --git a/tests/PhpWordTests/Element/FieldTest.php b/tests/PhpWordTests/Element/FieldTest.php new file mode 100644 index 0000000000..f624e9294d --- /dev/null +++ b/tests/PhpWordTests/Element/FieldTest.php @@ -0,0 +1,146 @@ +getType()); + } + + /** + * New instance with type and properties. + */ + public function testConstructWithTypeProperties(): void + { + $oField = new Field('DATE', ['dateformat' => 'd-M-yyyy']); + + self::assertEquals('DATE', $oField->getType()); + self::assertEquals(['dateformat' => 'd-M-yyyy'], $oField->getProperties()); + } + + /** + * New instance with type and properties and options. + */ + public function testConstructWithTypePropertiesOptions(): void + { + $oField = new Field('DATE', ['dateformat' => 'd-M-yyyy'], ['SakaEraCalendar', 'PreserveFormat']); + + self::assertEquals('DATE', $oField->getType()); + self::assertEquals(['dateformat' => 'd-M-yyyy'], $oField->getProperties()); + self::assertEquals(['SakaEraCalendar', 'PreserveFormat'], $oField->getOptions()); + } + + /** + * New instance with type and properties and options and text. + */ + public function testConstructWithTypePropertiesOptionsText(): void + { + $oField = new Field('XE', [], ['Bold', 'Italic'], 'FieldValue'); + + self::assertEquals('XE', $oField->getType()); + self::assertEquals([], $oField->getProperties()); + self::assertEquals(['Bold', 'Italic'], $oField->getOptions()); + self::assertEquals('FieldValue', $oField->getText()); + } + + /** + * New instance with type and properties and options and text as TextRun. + */ + public function testConstructWithTypePropertiesOptionsTextAsTextRun(): void + { + $textRun = new TextRun(); + $textRun->addText('test string'); + + $oField = new Field('XE', [], ['Bold', 'Italic'], $textRun); + + self::assertEquals('XE', $oField->getType()); + self::assertEquals([], $oField->getProperties()); + self::assertEquals(['Bold', 'Italic'], $oField->getOptions()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $oField->getText()); + } + + public function testConstructWithOptionValue(): void + { + $oField = new Field('INDEX', [], ['\\c "3" \\h "A"']); + + self::assertEquals('INDEX', $oField->getType()); + self::assertEquals([], $oField->getProperties()); + self::assertEquals(['\\c "3" \\h "A"'], $oField->getOptions()); + } + + /** + * Test setType exception. + */ + public function testSetTypeException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type'); + $object = new Field(); + $object->setType('foo'); + } + + /** + * Test setProperties exception. + */ + public function testSetPropertiesException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid property'); + $object = new Field('PAGE'); + $object->setProperties(['foo' => 'bar']); + } + + /** + * Test setOptions exception. + */ + public function testSetOptionsException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid option'); + $object = new Field('PAGE'); + $object->setOptions(['foo' => 'bar']); + } + + /** + * Test setText exception. + */ + public function testSetTextException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid text'); + $object = new Field('XE'); + $object->setText([]); + } +} diff --git a/tests/PhpWordTests/Element/FooterTest.php b/tests/PhpWordTests/Element/FooterTest.php new file mode 100644 index 0000000000..f97b159cb9 --- /dev/null +++ b/tests/PhpWordTests/Element/FooterTest.php @@ -0,0 +1,176 @@ +getSectionId()); + } + + /** + * Add text. + */ + public function testAddText(): void + { + $oFooter = new Footer(1); + $element = $oFooter->addText('text'); + + self::assertCount(1, $oFooter->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + } + + /** + * Add text non-UTF8. + */ + public function testAddTextNotUTF8(): void + { + $oFooter = new Footer(1); + $element = $oFooter->addText(utf8decode('ééé')); + + self::assertCount(1, $oFooter->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + self::assertEquals('ééé', $element->getText()); + } + + /** + * Add text break. + */ + public function testAddTextBreak(): void + { + $oFooter = new Footer(1); + $iVal = mt_rand(1, 1000); + $oFooter->addTextBreak($iVal); + + self::assertCount($iVal, $oFooter->getElements()); + } + + /** + * Add text run. + */ + public function testCreateTextRun(): void + { + $oFooter = new Footer(1); + $element = $oFooter->addTextRun(); + + self::assertCount(1, $oFooter->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $element); + } + + /** + * Add table. + */ + public function testAddTable(): void + { + $oFooter = new Footer(1); + $element = $oFooter->addTable(); + + self::assertCount(1, $oFooter->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Table', $element); + } + + /** + * Add image. + */ + public function testAddImage(): void + { + $src = __DIR__ . '/../_files/images/earth.jpg'; + $oFooter = new Footer(1); + $element = $oFooter->addImage($src); + + self::assertCount(1, $oFooter->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); + } + + /** + * Add image by URL. + */ + public function testAddImageByUrl(): void + { + $oFooter = new Footer(1); + $element = $oFooter->addImage(self::getRemoteGifImageUrl()); + + self::assertCount(1, $oFooter->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); + } + + /** + * Add preserve text. + */ + public function testAddPreserveText(): void + { + $oFooter = new Footer(1); + $element = $oFooter->addPreserveText('text'); + + self::assertCount(1, $oFooter->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); + } + + /** + * Add preserve text non-UTF8. + */ + public function testAddPreserveTextNotUTF8(): void + { + $oFooter = new Footer(1); + $element = $oFooter->addPreserveText(utf8decode('ééé')); + + self::assertCount(1, $oFooter->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); + self::assertEquals(['ééé'], $element->getText()); + } + + /** + * Get elements. + */ + public function testGetElements(): void + { + $oFooter = new Footer(1); + + self::assertIsArray($oFooter->getElements()); + } + + /** + * Set/get relation Id. + */ + public function testRelationID(): void + { + $oFooter = new Footer(0); + + $iVal = mt_rand(1, 1000); + $oFooter->setRelationId($iVal); + + self::assertEquals($iVal, $oFooter->getRelationId()); + self::assertEquals(Footer::AUTO, $oFooter->getType()); + } +} diff --git a/tests/PhpWordTests/Element/FootnoteTest.php b/tests/PhpWordTests/Element/FootnoteTest.php new file mode 100644 index 0000000000..c6297cfc32 --- /dev/null +++ b/tests/PhpWordTests/Element/FootnoteTest.php @@ -0,0 +1,119 @@ +getElements()); + self::assertNull($oFootnote->getParagraphStyle()); + } + + /** + * New instance with string parameter. + */ + public function testConstructString(): void + { + $oFootnote = new Footnote('pStyle'); + + self::assertEquals('pStyle', $oFootnote->getParagraphStyle()); + } + + /** + * New instance with array parameter. + */ + public function testConstructArray(): void + { + $oFootnote = new Footnote(['spacing' => 100]); + + self::assertInstanceOf( + 'PhpOffice\\PhpWord\\Style\\Paragraph', + $oFootnote->getParagraphStyle() + ); + } + + /** + * Add text element. + */ + public function testAddText(): void + { + $oFootnote = new Footnote(); + $element = $oFootnote->addText('text'); + + self::assertCount(1, $oFootnote->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + } + + /** + * Add text break element. + */ + public function testAddTextBreak(): void + { + $oFootnote = new Footnote(); + $oFootnote->addTextBreak(2); + + self::assertCount(2, $oFootnote->getElements()); + } + + /** + * Add link element. + */ + public function testAddLink(): void + { + $oFootnote = new Footnote(); + $element = $oFootnote->addLink('https://github.com/PHPOffice/PHPWord'); + + self::assertCount(1, $oFootnote->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); + } + + /** + * Set/get reference Id. + */ + public function testReferenceId(): void + { + $oFootnote = new Footnote(); + + $iVal = mt_rand(1, 1000); + $oFootnote->setRelationId($iVal); + self::assertEquals($iVal, $oFootnote->getRelationId()); + } + + /** + * Get elements. + */ + public function testGetElements(): void + { + $oFootnote = new Footnote(); + self::assertIsArray($oFootnote->getElements()); + } +} diff --git a/tests/PhpWordTests/Element/FormulaTest.php b/tests/PhpWordTests/Element/FormulaTest.php new file mode 100644 index 0000000000..dcb730e9cc --- /dev/null +++ b/tests/PhpWordTests/Element/FormulaTest.php @@ -0,0 +1,54 @@ +add(new Element\Fraction( + new Element\Numeric(2), + new Element\Identifier('π') + )); + + $element = new Formula(new Math()); + + self::assertEquals(new Math(), $element->getMath()); + self::assertNotEquals($math, $element->getMath()); + + $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 new file mode 100644 index 0000000000..6d659dbd7c --- /dev/null +++ b/tests/PhpWordTests/Element/HeaderTest.php @@ -0,0 +1,253 @@ +getSectionId()); + self::assertEquals(Header::AUTO, $oHeader->getType()); + } + + /** + * Add text. + */ + public function testAddText(): void + { + $oHeader = new Header(1); + $element = $oHeader->addText('text'); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + self::assertCount(1, $oHeader->getElements()); + self::assertEquals('text', $element->getText()); + } + + /** + * Add text non-UTF8. + */ + public function testAddTextNotUTF8(): void + { + $oHeader = new Header(1); + $element = $oHeader->addText(utf8decode('ééé')); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + self::assertCount(1, $oHeader->getElements()); + self::assertEquals('ééé', $element->getText()); + } + + /** + * Add text break. + */ + public function testAddTextBreak(): void + { + $oHeader = new Header(1); + $oHeader->addTextBreak(); + self::assertCount(1, $oHeader->getElements()); + } + + /** + * Add text break with params. + */ + public function testAddTextBreakWithParams(): void + { + $oHeader = new Header(1); + $iVal = mt_rand(1, 1000); + $oHeader->addTextBreak($iVal); + self::assertCount($iVal, $oHeader->getElements()); + } + + /** + * Add text run. + */ + public function testCreateTextRun(): void + { + $oHeader = new Header(1); + $element = $oHeader->addTextRun(); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $element); + self::assertCount(1, $oHeader->getElements()); + } + + /** + * Add table. + */ + public function testAddTable(): void + { + $oHeader = new Header(1); + $element = $oHeader->addTable(); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Table', $element); + self::assertCount(1, $oHeader->getElements()); + } + + /** + * Add image. + */ + public function testAddImage(): void + { + $src = __DIR__ . '/../_files/images/earth.jpg'; + $oHeader = new Header(1); + $element = $oHeader->addImage($src); + + self::assertCount(1, $oHeader->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); + } + + /** + * Add image by URL. + */ + public function testAddImageByUrl(): void + { + $oHeader = new Header(1); + $element = $oHeader->addImage(self::getRemoteGifImageUrl()); + + self::assertCount(1, $oHeader->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); + } + + /** + * Add preserve text. + */ + public function testAddPreserveText(): void + { + $oHeader = new Header(1); + $element = $oHeader->addPreserveText('text'); + + self::assertCount(1, $oHeader->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); + } + + /** + * Add preserve text non-UTF8. + */ + public function testAddPreserveTextNotUTF8(): void + { + $oHeader = new Header(1); + $element = $oHeader->addPreserveText(utf8decode('ééé')); + + self::assertCount(1, $oHeader->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); + self::assertEquals(['ééé'], $element->getText()); + } + + /** + * Add watermark. + */ + public function testAddWatermark(): void + { + $src = __DIR__ . '/../_files/images/earth.jpg'; + $oHeader = new Header(1); + $element = $oHeader->addWatermark($src); + + self::assertCount(1, $oHeader->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); + } + + /** + * Get elements. + */ + public function testGetElements(): void + { + $oHeader = new Header(1); + + self::assertIsArray($oHeader->getElements()); + } + + /** + * Set/get relation Id. + */ + public function testRelationId(): void + { + $oHeader = new Header(1); + + $iVal = mt_rand(1, 1000); + $oHeader->setRelationId($iVal); + self::assertEquals($iVal, $oHeader->getRelationId()); + } + + /** + * Reset type. + */ + public function testResetType(): void + { + $oHeader = new Header(1); + $oHeader->firstPage(); + $oHeader->resetType(); + + self::assertEquals(Header::AUTO, $oHeader->getType()); + } + + /** + * First page. + */ + public function testFirstPage(): void + { + $oHeader = new Header(1); + $oHeader->firstPage(); + + self::assertEquals(Header::FIRST, $oHeader->getType()); + } + + /** + * Even page. + */ + public function testEvenPage(): void + { + $oHeader = new Header(1); + $oHeader->evenPage(); + + self::assertEquals(Header::EVEN, $oHeader->getType()); + } + + /** + * Add footnote exception. + */ + public function testAddFootnoteException(): void + { + $this->expectException(BadMethodCallException::class); + $header = new Header(1); + $header->addFootnote(); + } + + /** + * Set/get type. + */ + public function testSetGetType(): void + { + $object = new Header(1); + self::assertEquals(Header::AUTO, $object->getType()); + + $object->setType('ODD'); + self::assertEquals(Header::AUTO, $object->getType()); + } +} diff --git a/tests/PhpWordTests/Element/ImageTest.php b/tests/PhpWordTests/Element/ImageTest.php new file mode 100644 index 0000000000..f56b3da8a9 --- /dev/null +++ b/tests/PhpWordTests/Element/ImageTest.php @@ -0,0 +1,248 @@ +getSource()); + self::assertEquals(md5($src), $oImage->getMediaId()); + self::assertFalse($oImage->isWatermark()); + self::assertEquals(Image::SOURCE_LOCAL, $oImage->getSourceType()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Image', $oImage->getStyle()); + } + + /** + * New instance with style. + */ + public function testConstructWithStyle(): void + { + $src = __DIR__ . '/../_files/images/firefox.png'; + $oImage = new Image( + $src, + [ + 'width' => 210, + 'height' => 210, + 'alignment' => Jc::CENTER, + 'wrappingStyle' => \PhpOffice\PhpWord\Style\Image::WRAPPING_STYLE_BEHIND, + ] + ); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Image', $oImage->getStyle()); + } + + /** + * Valid image types. + * + * @dataProvider providerImages + */ + public function testImages($source, $type, $extension, $createFunction, $imageFunction, $imageQuality): void + { + $nam = ucfirst((string) strtok($source, '.')); + $source = __DIR__ . "/../_files/images/{$source}"; + $image = new Image($source, null, null, $nam); + 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], + ]; + } + + /** + * Get style. + */ + public function testStyle(): void + { + $oImage = new Image( + __DIR__ . '/../_files/images/earth.jpg', + ['height' => 210, 'alignment' => Jc::CENTER] + ); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Image', $oImage->getStyle()); + } + + /** + * Test invalid local image. + */ + public function testInvalidImageLocal(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\InvalidImageException::class); + new Image(__DIR__ . '/../_files/images/thisisnotarealimage'); + } + + /** + * Test invalid PHP Image. + */ + public function testInvalidImagePhp(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\InvalidImageException::class); + $object = new Image('test.php'); + $source = $object->getSource(); + } + + /** + * Test unsupported image. + */ + public function testUnsupportedImage(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\UnsupportedImageTypeException::class); + //disable ssl verification, never do this in real application, you should pass the certificiate instead!!! + $arrContextOptions = [ + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + ], + ]; + stream_context_set_default($arrContextOptions); + $object = new Image(self::getRemoteBmpImageUrl()); + $source = $object->getSource(); + } + + /** + * Get relation Id. + */ + public function testRelationID(): void + { + $oImage = new Image(__DIR__ . '/../_files/images/earth.jpg', ['width' => 100]); + $iVal = mt_rand(1, 1000); + $oImage->setRelationId($iVal); + self::assertEquals($iVal, $oImage->getRelationId()); + } + + /** + * Test archived image. + */ + public function testArchivedImage(): void + { + $archiveFile = __DIR__ . '/../_files/documents/reader.docx'; + $imageFile = 'word/media/image1.jpeg'; + $image = new Image("zip://{$archiveFile}#{$imageFile}"); + self::assertEquals('image/jpeg', $image->getImageType()); + } + + /** + * Test getting image as string. + */ + public function testImageAsStringFromFile(): void + { + $image = new Image(__DIR__ . '/../_files/images/earth.jpg'); + + self::assertNotNull($image->getImageStringData()); + self::assertNotNull($image->getImageStringData(true)); + } + + /** + * Test getting image from zip as string. + */ + public function testImageAsStringFromZip(): void + { + $archiveFile = __DIR__ . '/../_files/documents/reader.docx'; + $imageFile = 'word/media/image1.jpeg'; + $image = new Image("zip://{$archiveFile}#{$imageFile}"); + + self::assertNotNull($image->getImageStringData()); + self::assertNotNull($image->getImageStringData(true)); + } + + /** + * Test construct from string. + */ + public function testConstructFromString(): void + { + $source = file_get_contents(__DIR__ . '/../_files/images/earth.jpg'); + + $image = new Image($source); + self::assertEquals($source, $image->getSource()); + self::assertEquals(md5((string) $source), $image->getMediaId()); + self::assertEquals('image/jpeg', $image->getImageType()); + self::assertEquals('jpg', $image->getImageExtension()); + self::assertEquals('imagecreatefromstring', $image->getImageCreateFunction()); + self::assertNotNull($image->getImageFunction()); + self::assertEquals(100, $image->getImageQuality()); + self::assertTrue($image->isMemImage()); + + self::assertNotNull($image->getImageStringData()); + self::assertNotNull($image->getImageStringData(true)); + } + + /** + * Test construct from GD. + */ + public function testConstructFromGd(): void + { + $source = self::getRemoteImageUrl(); + + $image = new Image($source); + self::assertEquals($source, $image->getSource()); + self::assertEquals(md5($source), $image->getMediaId()); + self::assertEquals('image/png', $image->getImageType()); + self::assertEquals('png', $image->getImageExtension()); + self::assertEquals('imagecreatefrompng', $image->getImageCreateFunction()); + self::assertNotNull($image->getImageFunction()); + self::assertEquals(-1, $image->getImageQuality()); + self::assertTrue($image->isMemImage()); + + self::assertNotNull($image->getImageStringData()); + self::assertNotNull($image->getImageStringData(true)); + } + + /** + * Test invalid string image. + */ + public function testInvalidImageString(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\InvalidImageException::class); + $object = new Image('this_is-a_non_valid_image'); + $source = $object->getSource(); + } +} diff --git a/tests/PhpWord/Tests/Element/LineTest.php b/tests/PhpWordTests/Element/LineTest.php similarity index 69% rename from tests/PhpWord/Tests/Element/LineTest.php rename to tests/PhpWordTests/Element/LineTest.php index 5add9a6b07..f4a39b38d1 100644 --- a/tests/PhpWord/Tests/Element/LineTest.php +++ b/tests/PhpWordTests/Element/LineTest.php @@ -1,4 +1,5 @@ assertInstanceOf('PhpOffice\\PhpWord\\Element\\Line', $oLine); - $this->assertEquals($oLine->getStyle(), null); + self::assertNull($oLine->getStyle()); } /** - * Get style name + * Get style name. */ - public function testStyleText() + public function testStyleText(): void { $oLine = new Line('lineStyle'); - $this->assertEquals($oLine->getStyle(), 'lineStyle'); + self::assertEquals('lineStyle', $oLine->getStyle()); } /** - * Get style array + * Get style array. */ - public function testStyleArray() + public function testStyleArray(): void { $oLine = new Line( - array( + [ 'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(14), 'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(4), 'positioning' => 'absolute', @@ -67,10 +68,10 @@ public function testStyleArray() 'beginArrow' => \PhpOffice\PhpWord\Style\Line::ARROW_STYLE_BLOCK, 'endArrow' => \PhpOffice\PhpWord\Style\Line::ARROW_STYLE_OVAL, 'dash' => \PhpOffice\PhpWord\Style\Line::DASH_STYLE_LONG_DASH_DOT_DOT, - 'weight' => 10 - ) + 'weight' => 10, + ] ); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Line', $oLine->getStyle()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Line', $oLine->getStyle()); } } diff --git a/tests/PhpWordTests/Element/LinkTest.php b/tests/PhpWordTests/Element/LinkTest.php new file mode 100644 index 0000000000..5b5c6f77bf --- /dev/null +++ b/tests/PhpWordTests/Element/LinkTest.php @@ -0,0 +1,86 @@ +getSource()); + self::assertEquals($oLink->getSource(), $oLink->getText()); + self::assertNull($oLink->getFontStyle()); + self::assertNull($oLink->getParagraphStyle()); + } + + /** + * Create new instance with array. + */ + public function testConstructWithParamsArray(): void + { + $oLink = new Link( + 'https://github.com/PHPOffice/PHPWord', + 'PHPWord on GitHub', + ['color' => '0000FF', 'underline' => Font::UNDERLINE_SINGLE], + ['marginLeft' => 600, 'marginRight' => 600, 'marginTop' => 600, 'marginBottom' => 600] + ); + + self::assertEquals('https://github.com/PHPOffice/PHPWord', $oLink->getSource()); + self::assertEquals('PHPWord on GitHub', $oLink->getText()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oLink->getFontStyle()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oLink->getParagraphStyle()); + } + + /** + * Create new instance with style name string. + */ + public function testConstructWithParamsString(): void + { + $oLink = new Link('https://github.com/PHPOffice/PHPWord', null, 'fontStyle', 'paragraphStyle'); + + self::assertEquals('fontStyle', $oLink->getFontStyle()); + self::assertEquals('paragraphStyle', $oLink->getParagraphStyle()); + } + + /** + * Set/get relation Id. + */ + public function testRelationId(): void + { + $oLink = new Link('https://github.com/PHPOffice/PHPWord'); + + $iVal = mt_rand(1, 1000); + $oLink->setRelationId($iVal); + self::assertEquals($iVal, $oLink->getRelationId()); + } +} diff --git a/tests/PhpWordTests/Element/ListItemRunTest.php b/tests/PhpWordTests/Element/ListItemRunTest.php new file mode 100644 index 0000000000..69b5f990b0 --- /dev/null +++ b/tests/PhpWordTests/Element/ListItemRunTest.php @@ -0,0 +1,172 @@ +getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oListItemRun->getParagraphStyle()); + } + + /** + * New instance with string. + */ + public function testConstructString(): void + { + $oListItemRun = new ListItemRun(0, null, 'pStyle'); + + self::assertCount(0, $oListItemRun->getElements()); + self::assertEquals('pStyle', $oListItemRun->getParagraphStyle()); + } + + /** + * New instance with string. + */ + public function testConstructListString(): void + { + $oListItemRun = new ListItemRun(0, 'numberingStyle'); + + self::assertCount(0, $oListItemRun->getElements()); + } + + /** + * New instance with array. + */ + public function testConstructArray(): void + { + $oListItemRun = new ListItemRun(0, null, ['spacing' => 100]); + + self::assertCount(0, $oListItemRun->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oListItemRun->getParagraphStyle()); + } + + /** + * Get style. + */ + public function testStyle(): void + { + $oListItemRun = new ListItemRun(1, ['listType' => \PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER]); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\ListItem', $oListItemRun->getStyle()); + self::assertEquals(\PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER, $oListItemRun->getStyle()->getListType()); + } + + /** + * getDepth. + */ + public function testDepth(): void + { + $iVal = mt_rand(1, 1000); + $oListItemRun = new ListItemRun($iVal); + + self::assertEquals($iVal, $oListItemRun->getDepth()); + } + + /** + * Add text. + */ + public function testAddText(): void + { + $oListItemRun = new ListItemRun(); + $element = $oListItemRun->addText('text'); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + self::assertCount(1, $oListItemRun->getElements()); + self::assertEquals('text', $element->getText()); + } + + /** + * Add text non-UTF8. + */ + public function testAddTextNotUTF8(): void + { + $oListItemRun = new ListItemRun(); + $element = $oListItemRun->addText(utf8decode('ééé')); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + self::assertCount(1, $oListItemRun->getElements()); + self::assertEquals('ééé', $element->getText()); + } + + /** + * Add link. + */ + public function testAddLink(): void + { + $oListItemRun = new ListItemRun(); + $element = $oListItemRun->addLink('https://github.com/PHPOffice/PHPWord'); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); + self::assertCount(1, $oListItemRun->getElements()); + self::assertEquals('https://github.com/PHPOffice/PHPWord', $element->getSource()); + } + + /** + * Add link with name. + */ + public function testAddLinkWithName(): void + { + $oListItemRun = new ListItemRun(); + $element = $oListItemRun->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord on GitHub'); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); + self::assertCount(1, $oListItemRun->getElements()); + self::assertEquals('https://github.com/PHPOffice/PHPWord', $element->getSource()); + self::assertEquals('PHPWord on GitHub', $element->getText()); + } + + /** + * Add text break. + */ + public function testAddTextBreak(): void + { + $oListItemRun = new ListItemRun(); + $oListItemRun->addTextBreak(2); + + self::assertCount(2, $oListItemRun->getElements()); + } + + /** + * Add image. + */ + public function testAddImage(): void + { + $src = __DIR__ . '/../_files/images/earth.jpg'; + + $oListItemRun = new ListItemRun(); + $element = $oListItemRun->addImage($src); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); + self::assertCount(1, $oListItemRun->getElements()); + } +} diff --git a/tests/PhpWordTests/Element/ListItemTest.php b/tests/PhpWordTests/Element/ListItemTest.php new file mode 100644 index 0000000000..3da9a8d101 --- /dev/null +++ b/tests/PhpWordTests/Element/ListItemTest.php @@ -0,0 +1,63 @@ +getTextObject()); + } + + /** + * Get style. + */ + public function testStyle(): void + { + $oListItem = new ListItem('text', 1, null, ['listType' => \PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER]); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\ListItem', $oListItem->getStyle()); + self::assertEquals(\PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER, $oListItem->getStyle()->getListType()); + } + + /** + * Get depth. + */ + public function testDepth(): void + { + $iVal = mt_rand(1, 1000); + $oListItem = new ListItem('text', $iVal); + + self::assertEquals($iVal, $oListItem->getDepth()); + } +} diff --git a/tests/PhpWordTests/Element/ObjectTest.php b/tests/PhpWordTests/Element/ObjectTest.php new file mode 100644 index 0000000000..2ef05567bc --- /dev/null +++ b/tests/PhpWordTests/Element/ObjectTest.php @@ -0,0 +1,104 @@ +getStyle()); + self::assertEquals($src, $oObject->getSource()); + } + + /** + * Create new instance with supported files. + */ + public function testConstructWithSupportedFilesLong(): void + { + $src = __DIR__ . '/../_files/documents/sheet.xls'; + $oObject = new OLEObject($src); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Image', $oObject->getStyle()); + self::assertEquals($src, $oObject->getSource()); + } + + /** + * Create new instance with non-supported files. + */ + public function testConstructWithNotSupportedFiles(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\InvalidObjectException::class); + $src = __DIR__ . '/../_files/xsl/passthrough.xsl'; + $oObject = new OLEObject($src); + $source = $oObject->getSource(); + } + + /** + * Create with style. + */ + public function testConstructWithSupportedFilesAndStyle(): void + { + $src = __DIR__ . '/../_files/documents/sheet.xls'; + $oObject = new OLEObject($src, ['width' => '230px']); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Image', $oObject->getStyle()); + self::assertEquals($src, $oObject->getSource()); + } + + /** + * Set/get relation Id. + */ + public function testRelationId(): void + { + $src = __DIR__ . '/../_files/documents/sheet.xls'; + $oObject = new OLEObject($src); + + $iVal = mt_rand(1, 1000); + $oObject->setRelationId($iVal); + self::assertEquals($iVal, $oObject->getRelationId()); + } + + /** + * Set/get image relation Id. + */ + public function testImageRelationId(): void + { + $src = __DIR__ . '/../_files/documents/sheet.xls'; + $oObject = new OLEObject($src); + + $iVal = mt_rand(1, 1000); + $oObject->setImageRelationId($iVal); + self::assertEquals($iVal, $oObject->getImageRelationId()); + } +} diff --git a/tests/PhpWordTests/Element/PreserveTextTest.php b/tests/PhpWordTests/Element/PreserveTextTest.php new file mode 100644 index 0000000000..746db6aeee --- /dev/null +++ b/tests/PhpWordTests/Element/PreserveTextTest.php @@ -0,0 +1,63 @@ +getText()); + self::assertNull($oPreserveText->getFontStyle()); + self::assertNull($oPreserveText->getParagraphStyle()); + } + + /** + * Create new instance with style name. + */ + public function testConstructWithString(): void + { + $oPreserveText = new PreserveText('text', 'styleFont', 'styleParagraph'); + self::assertEquals(['text'], $oPreserveText->getText()); + self::assertEquals('styleFont', $oPreserveText->getFontStyle()); + self::assertEquals('styleParagraph', $oPreserveText->getParagraphStyle()); + } + + /** + * Create new instance with array. + */ + public function testConstructWithArray(): void + { + $oPreserveText = new PreserveText('text', ['size' => 16, 'color' => '1B2232'], ['alignment' => Jc::CENTER]); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oPreserveText->getFontStyle()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oPreserveText->getParagraphStyle()); + } +} diff --git a/tests/PhpWordTests/Element/RowTest.php b/tests/PhpWordTests/Element/RowTest.php new file mode 100644 index 0000000000..6dc8335ffd --- /dev/null +++ b/tests/PhpWordTests/Element/RowTest.php @@ -0,0 +1,68 @@ +getHeight()); + self::assertIsArray($oRow->getCells()); + self::assertCount(0, $oRow->getCells()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Row', $oRow->getStyle()); + } + + /** + * Create new instance with parameters. + */ + public function testConstructWithParams(): void + { + $iVal = mt_rand(1, 1000); + $oRow = new Row($iVal, ['borderBottomSize' => 18, 'borderBottomColor' => '0000FF', 'bgColor' => '66BBFF']); + + self::assertEquals($iVal, $oRow->getHeight()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Row', $oRow->getStyle()); + } + + /** + * Add cell. + */ + public function testAddCell(): void + { + $oRow = new Row(); + $element = $oRow->addCell(); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Cell', $element); + self::assertCount(1, $oRow->getCells()); + } +} diff --git a/tests/PhpWordTests/Element/RubyTest.php b/tests/PhpWordTests/Element/RubyTest.php new file mode 100644 index 0000000000..44d2e5ba3c --- /dev/null +++ b/tests/PhpWordTests/Element/RubyTest.php @@ -0,0 +1,97 @@ +getBaseTextRun()->getText()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $ruby->getBaseTextRun()->getParagraphStyle()); + self::assertEquals('', $ruby->getRubyTextRun()->getText()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $ruby->getRubyTextRun()->getParagraphStyle()); + self::assertEquals(RubyProperties::ALIGNMENT_DISTRIBUTE_SPACE, $ruby->getProperties()->getAlignment()); + } + + /** + * Get/set base text. + */ + public function testBaseText(): void + { + $ruby = new Ruby(new TextRun(), new TextRun(), new RubyProperties()); + + self::assertEquals('', $ruby->getBaseTextRun()->getText()); + $tr = new TextRun(); + $tr->addText('Hello, world'); + $ruby->setBaseTextRun($tr); + self::assertEquals('Hello, world', $ruby->getBaseTextRun()->getText()); + } + + /** + * Get/set ruby text. + */ + public function testRubyText(): void + { + $ruby = new Ruby(new TextRun(), new TextRun(), new RubyProperties()); + + self::assertEquals('', $ruby->getRubyTextRun()->getText()); + $tr = new TextRun(); + $tr->addText('Hello, ruby'); + $ruby->setRubyTextRun($tr); + self::assertEquals('Hello, ruby', $ruby->getRubyTextRun()->getText()); + } + + /** + * Get/set ruby properties. + */ + public function testRubyProperties(): void + { + $ruby = new Ruby(new TextRun(), new TextRun(), new RubyProperties()); + + self::assertEquals(RubyProperties::ALIGNMENT_DISTRIBUTE_SPACE, $ruby->getProperties()->getAlignment()); + + $properties = new RubyProperties(); + $properties->setAlignment(RubyProperties::ALIGNMENT_RIGHT_VERTICAL); + $properties->setFontFaceSize(1); + $properties->setFontPointsAboveBaseText(2); + $properties->setFontSizeForBaseText(3); + $properties->setLanguageId('en-US'); + $ruby->setProperties($properties); + + self::assertEquals(RubyProperties::ALIGNMENT_RIGHT_VERTICAL, $ruby->getProperties()->getAlignment()); + self::assertEquals(1, $ruby->getProperties()->getFontFaceSize()); + self::assertEquals(2, $ruby->getProperties()->getFontPointsAboveBaseText()); + self::assertEquals(3, $ruby->getProperties()->getFontSizeForBaseText()); + self::assertEquals('en-US', $ruby->getProperties()->getLanguageId()); + } +} diff --git a/tests/PhpWordTests/Element/SDTTest.php b/tests/PhpWordTests/Element/SDTTest.php new file mode 100644 index 0000000000..d7617160be --- /dev/null +++ b/tests/PhpWordTests/Element/SDTTest.php @@ -0,0 +1,75 @@ +setValue($value); + $object->setListItems($types); + $object->setAlias($alias); + $object->setTag($tag); + + self::assertEquals($type, $object->getType()); + self::assertEquals($types, $object->getListItems()); + self::assertEquals($value, $object->getValue()); + self::assertEquals($alias, $object->getAlias()); + self::assertEquals($tag, $object->getTag()); + } + + /** + * Test set type exception. + */ + public function testSetTypeException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid style value'); + $object = new SDT('comboBox'); + $object->setType('foo'); + } + + /** + * Test set type. + */ + public function testSetTypeNull(): void + { + $object = new SDT('comboBox'); + $object->setType(' '); + + self::assertEquals('comboBox', $object->getType()); + } +} diff --git a/tests/PhpWordTests/Element/SectionTest.php b/tests/PhpWordTests/Element/SectionTest.php new file mode 100644 index 0000000000..bf38a33300 --- /dev/null +++ b/tests/PhpWordTests/Element/SectionTest.php @@ -0,0 +1,224 @@ +getStyle()); + } + + public function testConstructorWithArrayStyle(): void + { + $section = new Section(0, ['orientation' => 'landscape']); + $style = $section->getStyle(); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Section', $style); + self::assertEquals('landscape', $style->getOrientation()); + } + + public function testConstructorWithObjectStyle(): void + { + $style = new SectionStyle(); + $section = new Section(0, $style); + self::assertSame($style, $section->getStyle()); + } + + /** + * @covers ::setStyle + */ + public function testSetStyle(): void + { + $expected = 'landscape'; + $object = new Section(0); + $object->setStyle(['orientation' => $expected, 'foo' => null]); + self::assertEquals($expected, $object->getStyle()->getOrientation()); + } + + /** + * @coversNothing + */ + public function testAddElements(): void + { + $objectSource = __DIR__ . '/../_files/documents/reader.docx'; + $imageSource = __DIR__ . '/../_files/images/PhpWord.png'; + + $section = new Section(0); + $section->setPhpWord(new PhpWord()); + $section->addText(utf8decode('ä')); + $section->addLink(utf8decode('http://xn--4caaa.com/'), utf8decode('ä')); + $section->addTextBreak(); + $section->addPageBreak(); + $section->addTable(); + $section->addListItem(utf8decode('ä')); + $section->addObject($objectSource); + $section->addImage($imageSource); + $section->addTitle(utf8decode('ä'), 1); + $section->addTextRun(); + $section->addFootnote(); + $section->addCheckBox(utf8decode('chkä'), utf8decode('Contentä')); + $section->addTOC(); + + $elementCollection = $section->getElements(); + $elementTypes = [ + 'Text', + 'Link', + 'TextBreak', + 'PageBreak', + 'Table', + 'ListItem', + 'OLEObject', + 'Image', + 'Title', + 'TextRun', + 'Footnote', + 'CheckBox', + 'TOC', + ]; + $elmCount = 0; + foreach ($elementTypes as $elementType) { + self::assertInstanceOf("PhpOffice\\PhpWord\\Element\\{$elementType}", $elementCollection[$elmCount]); + ++$elmCount; + } + } + + /** + * @coversNothing + */ + public function testAddObjectException(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\InvalidObjectException::class); + $source = __DIR__ . '/_files/xsl/passthrough.xsl'; + $section = new Section(0); + $section->addObject($source); + } + + /** + * Add title with predefined style. + * + * @coversNothing + */ + public function testAddTitleWithStyle(): void + { + Style::addTitleStyle(1, ['size' => 14]); + $section = new Section(0); + $section->setPhpWord(new PhpWord()); + $section->addTitle('Test', 1); + $elementCollection = $section->getElements(); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Title', $elementCollection[0]); + } + + /** + * @covers ::addFooter + * @covers ::addHeader + * @covers ::hasDifferentFirstPage + */ + public function testAddHeaderFooter(): void + { + $object = new Section(0); + $elements = ['Header', 'Footer']; + + foreach ($elements as $element) { + $method = "add{$element}"; + self::assertInstanceOf("PhpOffice\\PhpWord\\Element\\{$element}", $object->$method()); + } + self::assertFalse($object->hasDifferentFirstPage()); + } + + /** + * @covers ::addHeader + * @covers ::hasDifferentFirstPage + */ + public function testHasDifferentFirstPageFooter(): void + { + $object = new Section(1); + $object->addFooter(Header::FIRST); + self::assertTrue($object->hasDifferentFirstPage()); + } + + /** + * @covers ::addHeader + * @covers ::hasDifferentFirstPage + */ + public function testHasDifferentFirstPage(): void + { + $object = new Section(1); + $header = $object->addHeader(); + $header->setType(Header::FIRST); + self::assertTrue($object->hasDifferentFirstPage()); + } + + /** + * @covers ::addHeader + */ + public function testAddHeaderException(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Invalid header/footer type.'); + $object = new Section(1); + $object->addHeader('ODD'); + } + + /** + * @covers \PhpOffice\PhpWord\Element\AbstractContainer::removeElement + */ + public function testRemoveElementByIndex(): void + { + $section = new Section(1); + $section->addText('firstText'); + $section->addText('secondText'); + + self::assertEquals(2, $section->countElements()); + $section->removeElement(1); + + self::assertEquals(1, $section->countElements()); + } + + /** + * @covers \PhpOffice\PhpWord\Element\AbstractContainer::removeElement + */ + public function testRemoveElementByElement(): void + { + $section = new Section(1); + $firstText = $section->addText('firstText'); + $secondText = $section->addText('secondText'); + + self::assertEquals(2, $section->countElements()); + $section->removeElement($firstText); + + self::assertEquals(1, $section->countElements()); + self::assertEquals($secondText->getElementId(), $section->getElement(1)->getElementId()); + } +} diff --git a/tests/PhpWord/Tests/Element/TOCTest.php b/tests/PhpWordTests/Element/TOCTest.php similarity index 52% rename from tests/PhpWord/Tests/Element/TOCTest.php rename to tests/PhpWordTests/Element/TOCTest.php index d0ebf343fe..3770823f5f 100644 --- a/tests/PhpWord/Tests/Element/TOCTest.php +++ b/tests/PhpWordTests/Element/TOCTest.php @@ -1,4 +1,5 @@ 9062, + $expected = [ + 'position' => 9062, 'leader' => \PhpOffice\PhpWord\Style\Tab::TAB_LEADER_DOT, - 'indent' => 200, - ); - $object = new TOC(array('size' => 11), array('position' => $expected['position'])); + 'indent' => 200, + ]; + $object = new TOC(['size' => 11], ['position' => $expected['position']]); $tocStyle = $object->getStyleTOC(); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\TOC', $tocStyle); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $object->getStyleFont()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\TOC', $tocStyle); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $object->getStyleFont()); foreach ($expected as $key => $value) { $method = "get{$key}"; - $this->assertEquals($value, $tocStyle->$method()); + self::assertEquals($value, $tocStyle->$method()); } } /** - * Construct with named font style + * Construct with named font style. */ - public function testConstructWithStyleName() + public function testConstructWithStyleName(): void { $object = new TOC('Font Style'); // $tocStyle = $object->getStyleTOC(); - $this->assertEquals('Font Style', $object->getStyleFont()); + self::assertEquals('Font Style', $object->getStyleFont()); } /** - * Test when no PHPWord object is assigned: + * Test when no PHPWord object is assigned:. */ - public function testNoPhpWord() + public function testNoPhpWord(): void { $object = new TOC(); - $this->assertEmpty($object->getTitles()); - $this->assertNull($object->getPhpWord()); + self::assertEmpty($object->getTitles()); + self::assertNull($object->getPhpWord()); } /** - * Set/get minDepth and maxDepth + * Set/get minDepth and maxDepth. */ - public function testSetGetMinMaxDepth() + public function testSetGetMinMaxDepth(): void { - $titles = array( + $titles = [ 'Heading 1' => 1, 'Heading 2' => 2, 'Heading 3' => 3, 'Heading 4' => 4, - ); + ]; $phpWord = new PhpWord(); foreach ($titles as $text => $depth) { @@ -90,14 +91,14 @@ public function testSetGetMinMaxDepth() } $toc = new TOC(); $toc->setPhpWord($phpWord); - $this->assertEquals(1, $toc->getMinDepth()); - $this->assertEquals(9, $toc->getMaxDepth()); + self::assertEquals(1, $toc->getMinDepth()); + self::assertEquals(9, $toc->getMaxDepth()); $toc->setMinDepth(2); $toc->setMaxDepth(3); $toc->getTitles(); - $this->assertEquals(2, $toc->getMinDepth()); - $this->assertEquals(3, $toc->getMaxDepth()); + self::assertEquals(2, $toc->getMinDepth()); + self::assertEquals(3, $toc->getMaxDepth()); } } diff --git a/tests/PhpWordTests/Element/TableTest.php b/tests/PhpWordTests/Element/TableTest.php new file mode 100644 index 0000000000..0628d269e6 --- /dev/null +++ b/tests/PhpWordTests/Element/TableTest.php @@ -0,0 +1,111 @@ +getStyle()); + self::assertNull($oTable->getWidth()); + self::assertEquals([], $oTable->getRows()); + self::assertCount(0, $oTable->getRows()); + } + + /** + * Get style name. + */ + public function testStyleText(): void + { + $oTable = new Table('tableStyle'); + + self::assertEquals('tableStyle', $oTable->getStyle()); + } + + /** + * Get style array. + */ + public function testStyleArray(): void + { + $oTable = new Table(['borderSize' => 6, 'borderColor' => '006699', 'cellMargin' => 80]); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Table', $oTable->getStyle()); + } + + /** + * Set/get width. + */ + public function testWidth(): void + { + $oTable = new Table(); + $iVal = mt_rand(1, 1000); + $oTable->setWidth($iVal); + self::assertEquals($iVal, $oTable->getWidth()); + } + + /** + * Add/get row. + */ + public function testRow(): void + { + $oTable = new Table(); + $element = $oTable->addRow(); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Row', $element); + self::assertCount(1, $oTable->getRows()); + } + + /** + * Add cell. + */ + public function testCell(): void + { + $oTable = new Table(); + $oTable->addRow(); + $element = $oTable->addCell(); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Cell', $element); + } + + /** + * Add cell. + */ + public function testCountColumns(): void + { + $oTable = new Table(); + $oTable->addRow(); + $oTable->addCell(); + self::assertEquals($oTable->countColumns(), 1); + $oTable->addCell(); + $oTable->addCell(); + self::assertEquals($oTable->countColumns(), 3); + } +} diff --git a/tests/PhpWord/Tests/Element/TextBoxTest.php b/tests/PhpWordTests/Element/TextBoxTest.php similarity index 63% rename from tests/PhpWord/Tests/Element/TextBoxTest.php rename to tests/PhpWordTests/Element/TextBoxTest.php index 2c6da4655d..9289d1ad47 100644 --- a/tests/PhpWord/Tests/Element/TextBoxTest.php +++ b/tests/PhpWordTests/Element/TextBoxTest.php @@ -1,4 +1,5 @@ assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextBox', $oTextBox); - $this->assertEquals($oTextBox->getStyle(), null); + self::assertNull($oTextBox->getStyle()); } /** - * Get style name + * Get style name. */ - public function testStyleText() + public function testStyleText(): void { $oTextBox = new TextBox('textBoxStyle'); - $this->assertEquals($oTextBox->getStyle(), 'textBoxStyle'); + self::assertEquals('textBoxStyle', $oTextBox->getStyle()); } /** - * Get style array + * Get style array. */ - public function testStyleArray() + public function testStyleArray(): void { $oTextBox = new TextBox( - array( + [ 'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(4.5), 'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(17.5), 'positioning' => 'absolute', @@ -63,10 +64,10 @@ public function testStyleArray() 'stroke' => 0, 'innerMargin' => 0, 'borderSize' => 1, - 'borderColor' => '' - ) + 'borderColor' => '', + ] ); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\TextBox', $oTextBox->getStyle()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\TextBox', $oTextBox->getStyle()); } } diff --git a/tests/PhpWord/Tests/Element/TextBreakTest.php b/tests/PhpWordTests/Element/TextBreakTest.php similarity index 50% rename from tests/PhpWord/Tests/Element/TextBreakTest.php rename to tests/PhpWordTests/Element/TextBreakTest.php index 1d9479ee3e..10ca058180 100644 --- a/tests/PhpWord/Tests/Element/TextBreakTest.php +++ b/tests/PhpWordTests/Element/TextBreakTest.php @@ -1,4 +1,5 @@ assertNull($object->getFontStyle()); - $this->assertNull($object->getParagraphStyle()); + self::assertNull($object->getFontStyle()); + self::assertNull($object->getParagraphStyle()); } /** - * Construct with style object + * Construct with style object. */ - public function testConstructWithStyleObject() + public function testConstructWithStyleObject(): void { $fStyle = new Font(); $pStyle = new Paragraph(); $object = new TextBreak($fStyle, $pStyle); - $this->assertEquals($fStyle, $object->getFontStyle()); - $this->assertEquals($pStyle, $object->getParagraphStyle()); + self::assertEquals($fStyle, $object->getFontStyle()); + self::assertEquals($pStyle, $object->getParagraphStyle()); } /** - * Construct with style array + * Construct with style array. */ - public function testConstructWithStyleArray() + public function testConstructWithStyleArray(): void { - $fStyle = array('size' => 12); - $pStyle = array('spacing' => 240); + $fStyle = ['size' => 12]; + $pStyle = ['spacing' => 240]; $object = new TextBreak($fStyle, $pStyle); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $object->getFontStyle()); - $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $object->getParagraphStyle()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $object->getFontStyle()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $object->getParagraphStyle()); } /** - * Construct with style name + * Construct with style name. */ - public function testConstructWithStyleName() + public function testConstructWithStyleName(): void { $fStyle = 'fStyle'; $pStyle = 'pStyle'; $object = new TextBreak($fStyle, $pStyle); - $this->assertEquals($fStyle, $object->getFontStyle()); - $this->assertEquals($pStyle, $object->getParagraphStyle()); + self::assertEquals($fStyle, $object->getFontStyle()); + self::assertEquals($pStyle, $object->getParagraphStyle()); } } diff --git a/tests/PhpWordTests/Element/TextRunTest.php b/tests/PhpWordTests/Element/TextRunTest.php new file mode 100644 index 0000000000..b18eca99e4 --- /dev/null +++ b/tests/PhpWordTests/Element/TextRunTest.php @@ -0,0 +1,203 @@ +getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oTextRun->getParagraphStyle()); + } + + /** + * New instance with string. + */ + public function testConstructString(): void + { + $oTextRun = new TextRun('pStyle'); + + self::assertCount(0, $oTextRun->getElements()); + self::assertEquals('pStyle', $oTextRun->getParagraphStyle()); + } + + /** + * New instance with array. + */ + public function testConstructArray(): void + { + $oTextRun = new TextRun(['spacing' => 100]); + + self::assertCount(0, $oTextRun->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oTextRun->getParagraphStyle()); + } + + /** + * New instance with object. + */ + public function testConstructObject(): void + { + $oParagraphStyle = new Paragraph(); + $oParagraphStyle->setAlignment(Jc::BOTH); + $oTextRun = new TextRun($oParagraphStyle); + + self::assertCount(0, $oTextRun->getElements()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oTextRun->getParagraphStyle()); + self::assertEquals(Jc::BOTH, $oTextRun->getParagraphStyle()->getAlignment()); + } + + /** + * Add text. + */ + public function testAddText(): void + { + $oTextRun = new TextRun(); + $element = $oTextRun->addText('text'); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + self::assertCount(1, $oTextRun->getElements()); + self::assertEquals('text', $element->getText()); + } + + /** + * Add text non-UTF8. + */ + public function testAddTextNotUTF8(): void + { + $oTextRun = new TextRun(); + $element = $oTextRun->addText(utf8decode('ééé')); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); + self::assertCount(1, $oTextRun->getElements()); + self::assertEquals('ééé', $element->getText()); + } + + /** + * Add link. + */ + public function testAddLink(): void + { + $oTextRun = new TextRun(); + $element = $oTextRun->addLink('https://github.com/PHPOffice/PHPWord'); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); + self::assertCount(1, $oTextRun->getElements()); + self::assertEquals('https://github.com/PHPOffice/PHPWord', $element->getSource()); + } + + /** + * Add link with name. + */ + public function testAddLinkWithName(): void + { + $oTextRun = new TextRun(); + $element = $oTextRun->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord on GitHub'); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); + self::assertCount(1, $oTextRun->getElements()); + self::assertEquals('https://github.com/PHPOffice/PHPWord', $element->getSource()); + self::assertEquals('PHPWord on GitHub', $element->getText()); + } + + /** + * Add text break. + */ + public function testAddTextBreak(): void + { + $oTextRun = new TextRun(); + $oTextRun->addTextBreak(2); + + self::assertCount(2, $oTextRun->getElements()); + } + + /** + * Add image. + */ + public function testAddImage(): void + { + $src = __DIR__ . '/../_files/images/earth.jpg'; + + $oTextRun = new TextRun(); + $element = $oTextRun->addImage($src); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); + self::assertCount(1, $oTextRun->getElements()); + } + + /** + * Add footnote. + */ + public function testCreateFootnote(): void + { + $oTextRun = new TextRun(); + $oTextRun->setPhpWord(new PhpWord()); + $element = $oTextRun->addFootnote(); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Footnote', $element); + self::assertCount(1, $oTextRun->getElements()); + } + + /** + * Get paragraph style. + */ + public function testParagraph(): void + { + $oText = new TextRun('paragraphStyle'); + self::assertEquals('paragraphStyle', $oText->getParagraphStyle()); + + $oText->setParagraphStyle(['alignment' => Jc::CENTER, 'spaceAfter' => 100]); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oText->getParagraphStyle()); + } + + /** + * Add ruby element and get raw text. + */ + public function testRubyElementGetText(): void + { + $oTextRun = new TextRun(); + $oTextRun->setPhpWord(new PhpWord()); + + $properties = new RubyProperties(); + $baseTextRun = new TextRun(null); + $baseTextRun->addText('私'); + $rubyTextRun = new TextRun(null); + $rubyTextRun->addText('わたし'); + $element = $oTextRun->addRuby($baseTextRun, $rubyTextRun, $properties); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Ruby', $element); + self::assertCount(1, $oTextRun->getElements()); + self::assertEquals('私 (わたし)', $oTextRun->getText()); + } +} diff --git a/tests/PhpWordTests/Element/TextTest.php b/tests/PhpWordTests/Element/TextTest.php new file mode 100644 index 0000000000..693430a87f --- /dev/null +++ b/tests/PhpWordTests/Element/TextTest.php @@ -0,0 +1,87 @@ +getText()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oText->getFontStyle()); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oText->getParagraphStyle()); + } + + /** + * Get text. + */ + public function testText(): void + { + $oText = new Text('text'); + + self::assertEquals('text', $oText->getText()); + } + + /** + * Get font style. + */ + public function testFont(): void + { + $oText = new Text('text', 'fontStyle'); + self::assertEquals('fontStyle', $oText->getFontStyle()); + + $oText->setFontStyle(['bold' => true, 'italic' => true, 'size' => 16]); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', $oText->getFontStyle()); + } + + /** + * Get font style as object. + */ + public function testFontObject(): void + { + $font = new Font(); + $oText = new Text('text', $font); + self::assertEquals($font, $oText->getFontStyle()); + } + + /** + * Get paragraph style. + */ + public function testParagraph(): void + { + $oText = new Text('text', 'fontStyle', 'paragraphStyle'); + self::assertEquals('paragraphStyle', $oText->getParagraphStyle()); + + $oText->setParagraphStyle(['alignment' => Jc::CENTER, 'spaceAfter' => 100]); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', $oText->getParagraphStyle()); + } +} diff --git a/tests/PhpWordTests/Element/TitleTest.php b/tests/PhpWordTests/Element/TitleTest.php new file mode 100644 index 0000000000..ab1e38102f --- /dev/null +++ b/tests/PhpWordTests/Element/TitleTest.php @@ -0,0 +1,79 @@ +getText()); + self::assertEquals(1, $title->getDepth()); + self::assertNull($title->getPageNumber()); + self::assertNull($title->getStyle()); + } + + /** + * Create new instance with TextRun. + */ + public function testConstructWithTextRun(): void + { + $textRun = new TextRun(); + $textRun->addText('text'); + $title = new Title($textRun); + + 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); + + new Title(new PageBreak()); + } + + public function testConstructWithPageNumber(): void + { + $title = new Title('text', 1, 0); + + self::assertEquals('text', $title->getText()); + self::assertEquals(0, $title->getPageNumber()); + self::assertNull($title->getStyle()); + } +} diff --git a/tests/PhpWordTests/Element/TrackChangeTest.php b/tests/PhpWordTests/Element/TrackChangeTest.php new file mode 100644 index 0000000000..1d37fb3dd4 --- /dev/null +++ b/tests/PhpWordTests/Element/TrackChangeTest.php @@ -0,0 +1,65 @@ +setTrackChange($oTrackChange); + + self::assertEquals($author, $oTrackChange->getAuthor()); + self::assertEquals($date, $oTrackChange->getDate()); + self::assertEquals(TrackChange::INSERTED, $oTrackChange->getChangeType()); + } + + /** + * New instance with invalid \DateTime (produced by \DateTime::createFromFormat(...)). + */ + public function testConstructDefaultWithInvalidDate(): void + { + $author = 'Test User'; + $date = false; + $oTrackChange = new TrackChange(TrackChange::INSERTED, $author, $date); + + $oText = new Text('dummy text'); + $oText->setTrackChange($oTrackChange); + + self::assertEquals($author, $oTrackChange->getAuthor()); + self::assertEquals($date, null); + self::assertEquals(TrackChange::INSERTED, $oTrackChange->getChangeType()); + } +} diff --git a/tests/PhpWordTests/Escaper/RtfEscaper2Test.php b/tests/PhpWordTests/Escaper/RtfEscaper2Test.php new file mode 100644 index 0000000000..d65b543f21 --- /dev/null +++ b/tests/PhpWordTests/Escaper/RtfEscaper2Test.php @@ -0,0 +1,84 @@ +write()); + + return $txt2; + } + + public function expect($str) + { + return self::HEADER . $str . self::TRAILER; + } + + /** + * Test special characters which require escaping. + */ + public function testSpecial(): void + { + $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 + { + $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 + { + $str = 'Hebrew - שלום'; + $expect = $this->expect('Hebrew - \\uc0{\\u1513}\\uc0{\\u1500}\\uc0{\\u1493}\\uc0{\\u1501}'); + self::assertEquals($expect, $this->escapestring($str)); + } + + /** + * Test tab. + */ + public function testTab(): void + { + $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/Escaper/RtfEscaper3Test.php b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php new file mode 100644 index 0000000000..1aebea52f0 --- /dev/null +++ b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php @@ -0,0 +1,96 @@ +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/PhpWord/Tests/Exception/CopyFileExceptionTest.php b/tests/PhpWordTests/Exception/CopyFileExceptionTest.php similarity index 69% rename from tests/PhpWord/Tests/Exception/CopyFileExceptionTest.php rename to tests/PhpWordTests/Exception/CopyFileExceptionTest.php index 3d92595f25..9ca06d2b09 100644 --- a/tests/PhpWord/Tests/Exception/CopyFileExceptionTest.php +++ b/tests/PhpWordTests/Exception/CopyFileExceptionTest.php @@ -1,4 +1,5 @@ expectException(CopyFileException::class); + throw new CopyFileException('C:\source\dummy.txt', 'C:\destination\dummy.txt'); } } diff --git a/tests/PhpWord/Tests/Exception/CreateTemporaryFileExceptionTest.php b/tests/PhpWordTests/Exception/CreateTemporaryFileExceptionTest.php similarity index 73% rename from tests/PhpWord/Tests/Exception/CreateTemporaryFileExceptionTest.php rename to tests/PhpWordTests/Exception/CreateTemporaryFileExceptionTest.php index 7cf0cb773d..31015c62a8 100644 --- a/tests/PhpWord/Tests/Exception/CreateTemporaryFileExceptionTest.php +++ b/tests/PhpWordTests/Exception/CreateTemporaryFileExceptionTest.php @@ -1,4 +1,5 @@ expectException(CreateTemporaryFileException::class); + throw new CreateTemporaryFileException(); } } diff --git a/tests/PhpWord/Tests/Exception/ExceptionTest.php b/tests/PhpWordTests/Exception/ExceptionTest.php similarity index 61% rename from tests/PhpWord/Tests/Exception/ExceptionTest.php rename to tests/PhpWordTests/Exception/ExceptionTest.php index 38d0f7f2c7..09ee933c86 100644 --- a/tests/PhpWord/Tests/Exception/ExceptionTest.php +++ b/tests/PhpWordTests/Exception/ExceptionTest.php @@ -1,4 +1,5 @@ expectException(Exception::class); + + throw new Exception(); } } diff --git a/tests/PhpWord/Tests/Exception/InvalidImageExceptionTest.php b/tests/PhpWordTests/Exception/InvalidImageExceptionTest.php similarity index 70% rename from tests/PhpWord/Tests/Exception/InvalidImageExceptionTest.php rename to tests/PhpWordTests/Exception/InvalidImageExceptionTest.php index 7c6303e306..dea167bb25 100644 --- a/tests/PhpWord/Tests/Exception/InvalidImageExceptionTest.php +++ b/tests/PhpWordTests/Exception/InvalidImageExceptionTest.php @@ -1,4 +1,5 @@ expectException(InvalidImageException::class); + + throw new InvalidImageException(); } } diff --git a/tests/PhpWord/Tests/Exception/InvalidStyleExceptionTest.php b/tests/PhpWordTests/Exception/InvalidStyleExceptionTest.php similarity index 70% rename from tests/PhpWord/Tests/Exception/InvalidStyleExceptionTest.php rename to tests/PhpWordTests/Exception/InvalidStyleExceptionTest.php index 3cff7376ff..7f2a1650e4 100644 --- a/tests/PhpWord/Tests/Exception/InvalidStyleExceptionTest.php +++ b/tests/PhpWordTests/Exception/InvalidStyleExceptionTest.php @@ -1,4 +1,5 @@ expectException(InvalidStyleException::class); + + throw new InvalidStyleException(); } } diff --git a/tests/PhpWord/Tests/Exception/UnsupportedImageTypeExceptionTest.php b/tests/PhpWordTests/Exception/UnsupportedImageTypeExceptionTest.php similarity index 62% rename from tests/PhpWord/Tests/Exception/UnsupportedImageTypeExceptionTest.php rename to tests/PhpWordTests/Exception/UnsupportedImageTypeExceptionTest.php index 0a1020eb4e..2252b874ef 100644 --- a/tests/PhpWord/Tests/Exception/UnsupportedImageTypeExceptionTest.php +++ b/tests/PhpWordTests/Exception/UnsupportedImageTypeExceptionTest.php @@ -1,4 +1,5 @@ expectException(UnsupportedImageTypeException::class); + + throw new UnsupportedImageTypeException(); } } diff --git a/tests/PhpWordTests/IOFactoryTest.php b/tests/PhpWordTests/IOFactoryTest.php new file mode 100644 index 0000000000..6a8d746bd0 --- /dev/null +++ b/tests/PhpWordTests/IOFactoryTest.php @@ -0,0 +1,133 @@ +expectException(Exception::class); + IOFactory::createWriter(new PhpWord(), 'Word2006'); + } + + /** + * Create existing reader. + */ + public function testExistingReaderCanBeCreated(): void + { + self::assertInstanceOf( + 'PhpOffice\\PhpWord\\Reader\\Word2007', + IOFactory::createReader('Word2007') + ); + } + + /** + * Create non-existing reader. + */ + public function testNonexistentReaderCanNotBeCreated(): void + { + $this->expectException(Exception::class); + IOFactory::createReader('Word2006'); + } + + /** + * Load document. + */ + public function testLoad(): void + { + $file = __DIR__ . '/_files/templates/blank.docx'; + self::assertInstanceOf( + 'PhpOffice\\PhpWord\\PhpWord', + 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/MediaTest.php b/tests/PhpWordTests/MediaTest.php new file mode 100644 index 0000000000..2eb60a858b --- /dev/null +++ b/tests/PhpWordTests/MediaTest.php @@ -0,0 +1,134 @@ +expectException(Exception::class); + $this->expectExceptionMessage('Image object not assigned.'); + Media::addElement('section', 'image', __DIR__ . '/_files/images/mars.jpg'); + } +} diff --git a/tests/PhpWordTests/Metadata/DocInfoTest.php b/tests/PhpWordTests/Metadata/DocInfoTest.php new file mode 100644 index 0000000000..d5c9eb5124 --- /dev/null +++ b/tests/PhpWordTests/Metadata/DocInfoTest.php @@ -0,0 +1,229 @@ +setCreator(); + self::assertEquals('', $oProperties->getCreator()); + + $oProperties->setCreator('AAA'); + self::assertEquals('AAA', $oProperties->getCreator()); + } + + /** + * Last modified by. + */ + public function testLastModifiedBy(): void + { + $oProperties = new DocInfo(); + $oProperties->setLastModifiedBy(); + self::assertEquals('', $oProperties->getLastModifiedBy()); + + $oProperties->setLastModifiedBy('AAA'); + self::assertEquals('AAA', $oProperties->getLastModifiedBy()); + } + + /** + * Created. + */ + public function testCreated(): void + { + $oProperties = new DocInfo(); + $oProperties->setCreated(); + self::assertEquals(time(), $oProperties->getCreated()); + + $iTime = time() + 3600; + $oProperties->setCreated($iTime); + self::assertEquals($iTime, $oProperties->getCreated()); + } + + /** + * Modified. + */ + public function testModified(): void + { + $oProperties = new DocInfo(); + $oProperties->setModified(); + self::assertEquals(time(), $oProperties->getModified()); + + $iTime = time() + 3600; + $oProperties->setModified($iTime); + self::assertEquals($iTime, $oProperties->getModified()); + } + + /** + * Title. + */ + public function testTitle(): void + { + $oProperties = new DocInfo(); + $oProperties->setTitle(); + self::assertEquals('', $oProperties->getTitle()); + + $oProperties->setTitle('AAA'); + self::assertEquals('AAA', $oProperties->getTitle()); + } + + /** + * Description. + */ + public function testDescription(): void + { + $oProperties = new DocInfo(); + $oProperties->setDescription(); + self::assertEquals('', $oProperties->getDescription()); + + $oProperties->setDescription('AAA'); + self::assertEquals('AAA', $oProperties->getDescription()); + } + + /** + * Subject. + */ + public function testSubject(): void + { + $oProperties = new DocInfo(); + $oProperties->setSubject(); + self::assertEquals('', $oProperties->getSubject()); + + $oProperties->setSubject('AAA'); + self::assertEquals('AAA', $oProperties->getSubject()); + } + + /** + * Keywords. + */ + public function testKeywords(): void + { + $oProperties = new DocInfo(); + $oProperties->setKeywords(); + self::assertEquals('', $oProperties->getKeywords()); + + $oProperties->setKeywords('AAA'); + self::assertEquals('AAA', $oProperties->getKeywords()); + } + + /** + * Category. + */ + public function testCategory(): void + { + $oProperties = new DocInfo(); + $oProperties->setCategory(); + self::assertEquals('', $oProperties->getCategory()); + + $oProperties->setCategory('AAA'); + self::assertEquals('AAA', $oProperties->getCategory()); + } + + /** + * Company. + */ + public function testCompany(): void + { + $oProperties = new DocInfo(); + $oProperties->setCompany(); + self::assertEquals('', $oProperties->getCompany()); + + $oProperties->setCompany('AAA'); + self::assertEquals('AAA', $oProperties->getCompany()); + } + + /** + * Manager. + */ + public function testManager(): void + { + $oProperties = new DocInfo(); + $oProperties->setManager(); + self::assertEquals('', $oProperties->getManager()); + + $oProperties->setManager('AAA'); + self::assertEquals('AAA', $oProperties->getManager()); + } + + /** + * Custom properties. + */ + public function testCustomProperty(): void + { + $oProperties = new DocInfo(); + $oProperties->setCustomProperty('key1', null); + $oProperties->setCustomProperty('key2', true); + $oProperties->setCustomProperty('key3', 3); + $oProperties->setCustomProperty('key4', 4.4); + $oProperties->setCustomProperty('key5', 'value5'); + self::assertEquals(DocInfo::PROPERTY_TYPE_STRING, $oProperties->getCustomPropertyType('key1')); + self::assertEquals(DocInfo::PROPERTY_TYPE_BOOLEAN, $oProperties->getCustomPropertyType('key2')); + self::assertEquals(DocInfo::PROPERTY_TYPE_INTEGER, $oProperties->getCustomPropertyType('key3')); + self::assertEquals(DocInfo::PROPERTY_TYPE_FLOAT, $oProperties->getCustomPropertyType('key4')); + self::assertEquals(DocInfo::PROPERTY_TYPE_STRING, $oProperties->getCustomPropertyType('key5')); + self::assertNull($oProperties->getCustomPropertyType('key6')); + self::assertNull($oProperties->getCustomPropertyValue('key1')); + self::assertTrue($oProperties->getCustomPropertyValue('key2')); + self::assertEquals(3, $oProperties->getCustomPropertyValue('key3')); + self::assertEquals(4.4, $oProperties->getCustomPropertyValue('key4')); + self::assertEquals('value5', $oProperties->getCustomPropertyValue('key5')); + self::assertNull($oProperties->getCustomPropertyValue('key6')); + self::assertTrue($oProperties->isCustomPropertySet('key5')); + self::assertNotTrue($oProperties->isCustomPropertySet('key6')); + self::assertEquals(['key1', 'key2', 'key3', 'key4', 'key5'], $oProperties->getCustomProperties()); + } + + /** + * Convert property. + */ + public function testConvertProperty(): void + { + self::assertEquals('', DocInfo::convertProperty('a', 'empty')); + self::assertNull(DocInfo::convertProperty('a', 'null')); + self::assertEquals(8, DocInfo::convertProperty('8', 'int')); + self::assertEquals(8, DocInfo::convertProperty('8.3', 'uint')); + self::assertEquals(8.3, DocInfo::convertProperty('8.3', 'decimal')); + self::assertEquals('8.3', DocInfo::convertProperty('8.3', 'lpstr')); + self::assertEquals(strtotime('10/11/2013'), DocInfo::convertProperty('10/11/2013', 'date')); + self::assertTrue(DocInfo::convertProperty('true', 'bool')); + self::assertNotTrue(DocInfo::convertProperty('1', 'bool')); + self::assertEquals('1', DocInfo::convertProperty('1', 'array')); + self::assertEquals('1', DocInfo::convertProperty('1', '')); + + self::assertEquals(DocInfo::PROPERTY_TYPE_INTEGER, DocInfo::convertPropertyType('int')); + self::assertEquals(DocInfo::PROPERTY_TYPE_INTEGER, DocInfo::convertPropertyType('uint')); + self::assertEquals(DocInfo::PROPERTY_TYPE_FLOAT, DocInfo::convertPropertyType('decimal')); + self::assertEquals(DocInfo::PROPERTY_TYPE_STRING, DocInfo::convertPropertyType('lpstr')); + self::assertEquals(DocInfo::PROPERTY_TYPE_DATE, DocInfo::convertPropertyType('date')); + self::assertEquals(DocInfo::PROPERTY_TYPE_BOOLEAN, DocInfo::convertPropertyType('bool')); + self::assertEquals(DocInfo::PROPERTY_TYPE_UNKNOWN, DocInfo::convertPropertyType('array')); + self::assertEquals(DocInfo::PROPERTY_TYPE_UNKNOWN, DocInfo::convertPropertyType('')); + } +} diff --git a/tests/PhpWordTests/Metadata/SettingsTest.php b/tests/PhpWordTests/Metadata/SettingsTest.php new file mode 100644 index 0000000000..0c5a6f1593 --- /dev/null +++ b/tests/PhpWordTests/Metadata/SettingsTest.php @@ -0,0 +1,246 @@ +setEvenAndOddHeaders(true); + self::assertTrue($oSettings->hasEvenAndOddHeaders()); + } + + /** + * HideGrammaticalErrors. + */ + public function testHideGrammaticalErrors(): void + { + $oSettings = new Settings(); + $oSettings->setHideGrammaticalErrors(true); + self::assertTrue($oSettings->hasHideGrammaticalErrors()); + } + + /** + * HideSpellingErrors. + */ + public function testHideSpellingErrors(): void + { + $oSettings = new Settings(); + $oSettings->setHideSpellingErrors(true); + self::assertTrue($oSettings->hasHideSpellingErrors()); + } + + /** + * DocumentProtection. + */ + public function testDocumentProtection(): void + { + $oSettings = new Settings(); + $oSettings->setDocumentProtection(new Protection('trackedChanges')); + self::assertNotNull($oSettings->getDocumentProtection()); + + self::assertEquals('trackedChanges', $oSettings->getDocumentProtection()->getEditing()); + } + + /** + * Test setting an invalid salt. + */ + public function testInvalidSalt(): void + { + $this->expectException(InvalidArgumentException::class); + $protection = new Protection(); + $protection->setSalt('123'); + } + + /** + * TrackRevistions. + */ + public function testTrackRevisions(): void + { + $oSettings = new Settings(); + $oSettings->setTrackRevisions(true); + self::assertTrue($oSettings->hasTrackRevisions()); + } + + /** + * DoNotTrackFormatting. + */ + public function testDoNotTrackFormatting(): void + { + $oSettings = new Settings(); + $oSettings->setDoNotTrackFormatting(true); + self::assertTrue($oSettings->hasDoNotTrackFormatting()); + } + + /** + * DoNotTrackMoves. + */ + public function testDoNotTrackMoves(): void + { + $oSettings = new Settings(); + $oSettings->setDoNotTrackMoves(true); + self::assertTrue($oSettings->hasDoNotTrackMoves()); + } + + /** + * ProofState. + */ + public function testProofState(): void + { + $proofState = new ProofState(); + $proofState->setGrammar(ProofState::CLEAN); + $proofState->setSpelling(ProofState::DIRTY); + + $oSettings = new Settings(); + $oSettings->setProofState($proofState); + self::assertNotNull($oSettings->getProofState()); + self::assertEquals(ProofState::CLEAN, $oSettings->getProofState()->getGrammar()); + self::assertEquals(ProofState::DIRTY, $oSettings->getProofState()->getSpelling()); + } + + public function testWrongProofStateGrammar(): void + { + $this->expectException(InvalidArgumentException::class); + $proofState = new ProofState(); + $proofState->setGrammar('wrong'); + } + + public function testWrongProofStateSpelling(): void + { + $this->expectException(InvalidArgumentException::class); + $proofState = new ProofState(); + $proofState->setSpelling('wrong'); + } + + /** + * Zoom as percentage. + */ + public function testZoomPercentage(): void + { + $oSettings = new Settings(); + $oSettings->setZoom(75); + self::assertEquals(75, $oSettings->getZoom()); + } + + /** + * Zoom as string. + */ + public function testZoomEnum(): void + { + $oSettings = new Settings(); + $oSettings->setZoom(Zoom::FULL_PAGE); + self::assertEquals('fullPage', $oSettings->getZoom()); + } + + /** + * Test Update Fields on update. + */ + public function testUpdateFields(): void + { + $oSettings = new Settings(); + $oSettings->setUpdateFields(true); + self::assertTrue($oSettings->hasUpdateFields()); + } + + public function testAutoHyphenation(): void + { + $oSettings = new Settings(); + $oSettings->setAutoHyphenation(true); + self::assertTrue($oSettings->hasAutoHyphenation()); + } + + public function testDefaultAutoHyphenation(): void + { + $oSettings = new Settings(); + self::assertNull($oSettings->hasAutoHyphenation()); + } + + public function testConsecutiveHyphenLimit(): void + { + $consecutiveHypenLimit = 2; + $oSettings = new Settings(); + $oSettings->setConsecutiveHyphenLimit($consecutiveHypenLimit); + self::assertSame($consecutiveHypenLimit, $oSettings->getConsecutiveHyphenLimit()); + } + + public function testDefaultConsecutiveHyphenLimit(): void + { + $oSettings = new Settings(); + self::assertNull($oSettings->getConsecutiveHyphenLimit()); + } + + public function testHyphenationZone(): void + { + $hyphenationZoneInTwip = 100; + $oSettings = new Settings(); + $oSettings->setHyphenationZone($hyphenationZoneInTwip); + self::assertSame($hyphenationZoneInTwip, $oSettings->getHyphenationZone()); + } + + public function testDefaultHyphenationZone(): void + { + $oSettings = new Settings(); + self::assertNull($oSettings->getHyphenationZone()); + } + + public function testDoNotHyphenateCaps(): void + { + $oSettings = new Settings(); + $oSettings->setDoNotHyphenateCaps(true); + self::assertTrue($oSettings->hasDoNotHyphenateCaps()); + } + + public function testDefaultDoNotHyphenateCaps(): void + { + $oSettings = new Settings(); + self::assertNull($oSettings->hasDoNotHyphenateCaps()); + } + + public function testBookFoldPrinting(): void + { + $oSettings = new Settings(); + + $oSettings->setBookFoldPrinting(true); + self::assertTrue($oSettings->hasBookFoldPrinting()); + + $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 new file mode 100644 index 0000000000..76919bbdf3 --- /dev/null +++ b/tests/PhpWordTests/PhpWordTest.php @@ -0,0 +1,240 @@ +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()); + } + + /** + * Test create/get section. + */ + public function testCreateGetSections(): void + { + $phpWord = new PhpWord(); + $phpWord->addSection(); + self::assertCount(1, $phpWord->getSections()); + } + + /** + * Test set/get default font name. + */ + public function testSetGetDefaultFontName(): void + { + $phpWord = new PhpWord(); + $fontName = 'Times New Roman'; + self::assertEquals(Settings::DEFAULT_FONT_NAME, $phpWord->getDefaultFontName()); + $phpWord->setDefaultFontName($fontName); + self::assertEquals($fontName, $phpWord->getDefaultFontName()); + } + + /** + * Test set/get default font size. + */ + public function testSetGetDefaultFontSize(): void + { + $phpWord = new PhpWord(); + $fontSize = 16; + self::assertEquals(Settings::DEFAULT_FONT_SIZE, $phpWord->getDefaultFontSize()); + $phpWord->setDefaultFontSize($fontSize); + self::assertEquals($fontSize, $phpWord->getDefaultFontSize()); + } + + /** + * Test set/get default asian font name. + */ + public function testSetGetDefaultAsianFontName(): void + { + $phpWord = new PhpWord(); + $fontName = 'Times New Roman'; + self::assertEquals(Settings::DEFAULT_FONT_NAME, $phpWord->getDefaultAsianFontName()); + $phpWord->setDefaultAsianFontName($fontName); + self::assertEquals($fontName, $phpWord->getDefaultAsianFontName()); + } + + /** + * Test set/get default font color. + */ + public function testSetGetDefaultFontColor(): void + { + $phpWord = new PhpWord(); + $fontColor = 'FF0000'; + self::assertEquals(Settings::DEFAULT_FONT_COLOR, $phpWord->getDefaultFontColor()); + $phpWord->setDefaultFontColor($fontColor); + self::assertEquals($fontColor, $phpWord->getDefaultFontColor()); + } + + /** + * Test set default paragraph style. + */ + public function testSetDefaultParagraphStyle(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultParagraphStyle([]); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', Style::getStyle('Normal')); + } + + /** + * Test add styles. + */ + public function testAddStyles(): void + { + $phpWord = new PhpWord(); + $styles = [ + 'Paragraph' => 'Paragraph', + 'Font' => 'Font', + 'Table' => 'Table', + 'Link' => 'Font', + ]; + foreach ($styles as $key => $value) { + $method = "add{$key}Style"; + $styleId = "{$key} Style"; + $phpWord->$method($styleId, []); + self::assertInstanceOf("PhpOffice\\PhpWord\\Style\\{$value}", Style::getStyle($styleId)); + } + } + + /** + * Test add title style. + */ + public function testAddTitleStyle(): void + { + $phpWord = new PhpWord(); + $titleLevel = 1; + $titleName = "Heading_{$titleLevel}"; + $phpWord->addTitleStyle($titleLevel, []); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', Style::getStyle($titleName)); + } + + /** + * Test save. + */ + public function testSave(): 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); + } + + /** + * Test calling undefined method. + */ + public function testCallUndefinedMethod(): void + { + $this->expectException(BadMethodCallException::class); + $this->expectExceptionMessage('is not defined'); + $phpWord = new PhpWord(); + $phpWord->undefinedMethod(); + } + + /** + * @covers \PhpOffice\PhpWord\PhpWord::getSection + */ + public function testGetNotExistingSection(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->getSection(0); + + self::assertNull($section); + } + + /** + * @covers \PhpOffice\PhpWord\PhpWord::getSection + */ + public function testGetSection(): void + { + $phpWord = new PhpWord(); + $phpWord->addSection(); + $section = $phpWord->getSection(0); + + self::assertNotNull($section); + } + + /** + * @covers \PhpOffice\PhpWord\PhpWord::sortSections + */ + public function testSortSections(): void + { + $phpWord = new PhpWord(); + $section1 = $phpWord->addSection(); + $section1->addText('test1'); + $section2 = $phpWord->addSection(); + $section2->addText('test2'); + $section2->addText('test3'); + + self::assertEquals(1, $phpWord->getSection(0)->countElements()); + self::assertEquals(2, $phpWord->getSection(1)->countElements()); + + $phpWord->sortSections(function ($a, $b) { + $numElementsInA = $a->countElements(); + $numElementsInB = $b->countElements(); + if ($numElementsInA === $numElementsInB) { + return 0; + } elseif ($numElementsInA > $numElementsInB) { + return -1; + } + + return 1; + }); + + self::assertEquals(2, $phpWord->getSection(0)->countElements()); + self::assertEquals(1, $phpWord->getSection(1)->countElements()); + } + + /** + * @covers \PhpOffice\PhpWord\PhpWord::getSettings + */ + public function testGetSettings(): void + { + $phpWord = new PhpWord(); + self::assertInstanceOf('PhpOffice\\PhpWord\\Metadata\\Settings', $phpWord->getSettings()); + } +} diff --git a/tests/PhpWord/Tests/Reader/HTMLTest.php b/tests/PhpWordTests/Reader/HTMLTest.php similarity index 65% rename from tests/PhpWord/Tests/Reader/HTMLTest.php rename to tests/PhpWordTests/Reader/HTMLTest.php index cb3dc55c29..7a35a06f78 100644 --- a/tests/PhpWord/Tests/Reader/HTMLTest.php +++ b/tests/PhpWordTests/Reader/HTMLTest.php @@ -1,4 +1,5 @@ assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); + self::assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); } /** - * Test load exception - * - * @expectedException \Exception - * @expectedExceptionMessage Cannot read + * Test load exception. */ - public function testLoadException() + public function testLoadException(): void { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Cannot read'); $filename = __DIR__ . '/../_files/documents/foo.html'; IOFactory::load($filename, 'HTML'); } diff --git a/tests/PhpWordTests/Reader/MsDocTest.php b/tests/PhpWordTests/Reader/MsDocTest.php new file mode 100644 index 0000000000..3552271823 --- /dev/null +++ b/tests/PhpWordTests/Reader/MsDocTest.php @@ -0,0 +1,155 @@ +canRead($filename)); + } + + /** + * Can read exception. + */ + public function testCanReadFailed(): void + { + $object = new MsDoc(); + $filename = __DIR__ . '/../_files/documents/foo.doc'; + self::assertFalse($object->canRead($filename)); + } + + public function testLoadBasic(): void + { + $filename = __DIR__ . '/../_files/documents/reader.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('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()); + } + + /** + * Test exception on not existing file. + */ + public function testFailIfFileNotReadable(): void + { + $this->expectException(Exception::class); + $filename = __DIR__ . '/../_files/documents/not_existing_reader.doc'; + IOFactory::load($filename, 'MsDoc'); + } + + /** + * Test exception on non OLE document. + */ + public function testFailIfFileNotOle(): void + { + $this->expectException(Exception::class); + $filename = __DIR__ . '/../_files/documents/reader.odt'; + IOFactory::load($filename, 'MsDoc'); + } +} diff --git a/tests/PhpWordTests/Reader/ODText/ODTextSectionTest.php b/tests/PhpWordTests/Reader/ODText/ODTextSectionTest.php new file mode 100644 index 0000000000..8567dbcbea --- /dev/null +++ b/tests/PhpWordTests/Reader/ODText/ODTextSectionTest.php @@ -0,0 +1,84 @@ +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 new file mode 100644 index 0000000000..c05705a439 --- /dev/null +++ b/tests/PhpWordTests/Reader/ODTextTest.php @@ -0,0 +1,68 @@ +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/PhpWord/Tests/Reader/RTFTest.php b/tests/PhpWordTests/Reader/RTFTest.php similarity index 65% rename from tests/PhpWord/Tests/Reader/RTFTest.php rename to tests/PhpWordTests/Reader/RTFTest.php index c495db6884..a8f472571b 100644 --- a/tests/PhpWord/Tests/Reader/RTFTest.php +++ b/tests/PhpWordTests/Reader/RTFTest.php @@ -1,4 +1,5 @@ assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); + self::assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); } /** - * Test load exception - * - * @expectedException \Exception - * @expectedExceptionMessage Cannot read + * Test load exception. */ - public function testLoadException() + public function testLoadException(): void { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Cannot read'); $filename = __DIR__ . '/../_files/documents/foo.rtf'; IOFactory::load($filename, 'RTF'); } diff --git a/tests/PhpWordTests/Reader/Word2007/ElementTest.php b/tests/PhpWordTests/Reader/Word2007/ElementTest.php new file mode 100644 index 0000000000..892e59adf7 --- /dev/null +++ b/tests/PhpWordTests/Reader/Word2007/ElementTest.php @@ -0,0 +1,679 @@ + + + + + + + + + + + + + + + Test node value + + + + + + + + + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + self::assertInstanceOf(Text::class, $elements[0]->getElement(0)); + $text = $elements[0]; + self::assertEquals('Test node value', trim($text->getElement(0)->getText())); + } + + /** + * Test reading of textbreak. + */ + public function testReadTextBreak(): void + { + $documentXml = ' + + + test string + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + /** @var \PhpOffice\PhpWord\Element\TextRun $textRun */ + $textRun = $elements[0]; + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextBreak', $textRun->getElement(0)); + self::assertInstanceOf(Text::class, $textRun->getElement(1)); + self::assertEquals('test string', $textRun->getElement(1)->getText()); + } + + /** + * Test reading content inside w:smartTag. + */ + public function testSmartTag(): void + { + $documentXml = ' + + + test string + + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + /** @var \PhpOffice\PhpWord\Element\TextRun $textRun */ + $textRun = $elements[0]; + self::assertInstanceOf(Text::class, $textRun->getElement(0)); + self::assertEquals('test string', $textRun->getElement(0)->getText()); + } + + /** + * Test reading of textbreak. + */ + public function testReadListItemRunWithFormatting(): void + { + $documentXml = ' + + + + + + + + Two + + + with + + + + + + bold + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $sections = $phpWord->getSection(0); + self::assertNull($sections->getElement(999)); + self::assertInstanceOf('PhpOffice\PhpWord\Element\ListItemRun', $sections->getElement(0)); + self::assertEquals(0, $sections->getElement(0)->getDepth()); + + $listElements = $sections->getElement(0)->getElements(); + /** @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()); + } + + /** + * Test reading track changes. + */ + public function testReadTrackChange(): void + { + $documentXml = ' + + One + + + + two + + + + + three + + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + /** @var \PhpOffice\PhpWord\Element\TextRun $elements */ + $textRun = $elements[0]; + + self::assertEquals('One ', $textRun->getElement(0)->getText()); + + self::assertEquals('two', $textRun->getElement(1)->getText()); + self::assertNotNull($textRun->getElement(1)->getTrackChange()); + /** @var TrackChange $trackChange */ + $trackChange = $textRun->getElement(1)->getTrackChange(); + self::assertEquals(TrackChange::DELETED, $trackChange->getChangeType()); + + self::assertEquals('three', $textRun->getElement(2)->getText()); + self::assertNotNull($textRun->getElement(2)->getTrackChange()); + /** @var TrackChange $trackChange */ + $trackChange = $textRun->getElement(2)->getTrackChange(); + self::assertEquals(TrackChange::INSERTED, $trackChange->getChangeType()); + } + + /** + * Test reading of tab. + */ + public function testReadTab(): void + { + $documentXml = ' + + One + + Two + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + /** @var \PhpOffice\PhpWord\Element\TextRun $textRun */ + $textRun = $elements[0]; + self::assertInstanceOf(Text::class, $textRun->getElement(0)); + self::assertEquals('One', $textRun->getElement(0)->getText()); + self::assertInstanceOf(Text::class, $textRun->getElement(1)); + self::assertEquals("\t", $textRun->getElement(1)->getText()); + self::assertInstanceOf(Text::class, $textRun->getElement(2)); + self::assertEquals('Two', $textRun->getElement(2)->getText()); + } + + /** + * Test reading Title style. + */ + public function testReadTitleStyle(): void + { + $documentXml = ' + + + + + This is a non formatted title + + + + + + + + This is a + + + + + + bold + + + title + + '; + + $stylesXml = ' + + + + + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml, 'styles' => $stylesXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Title', $elements[0]); + /** @var \PhpOffice\PhpWord\Element\Title $title */ + $title = $elements[0]; + self::assertEquals('Title', $title->getStyle()); + self::assertEquals('This is a non formatted title', $title->getText()); + + 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::class, $formattedTitle->getText()); + } + + /** + * Test reading of nested table. + */ + public function testReadNestedTable(): void + { + $documentXml = ' + + + + + + + ${Field} + + + + + + + + '; + + $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); + } + + /** + * Test reading Drawing. + */ + public function testReadDrawing(): void + { + $documentXml = ' + + + + + + + + + + + + + + + + + + + + + + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + } + + /** + * Test reading FormField - DROPDOWN. + */ + public function testReadFormFieldDropdown(): void + { + $documentXml = ' + + Reference + + + + + + + + + + + + + + + + + + + + FORMDROPDOWN + + + + + + + + + + + + + + + + + + + '; + + $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 = ' + + Fieldname + + + + + + + + + + + + + + + + FORMTEXT + + + + + + + + + + + + + + + + + + This is some sample text + + + + + + + + '; + + $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 = ' + + + + + + + + + + + + + + + + + + FORMCHECKBOX + + + + + + + + + + + + + + '; + + $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()); + } + + /** + * Test reading of ruby. + */ + public function testReadRuby(): void + { + $documentXml = ' + + + + + + + + + + + + + + + + わたし + + + + + + + + + + + + + '; + + $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\Ruby', $subElements[0]); + /** @var RubyProperties $rubyProperties */ + $rubyProperties = $subElements[0]->getProperties(); + self::assertEquals(RubyProperties::ALIGNMENT_DISTRIBUTE_SPACE, $rubyProperties->getAlignment()); + self::assertEquals(12, $rubyProperties->getFontFaceSize()); + self::assertEquals(22, $rubyProperties->getFontPointsAboveBaseText()); + self::assertEquals(24, $rubyProperties->getFontSizeForBaseText()); + self::assertEquals('ja-JP', $rubyProperties->getLanguageId()); + /** @var \PhpOffice\PhpWord\Element\TextRun $textRun */ + $textRun = $subElements[0]->getBaseTextRun(); + self::assertEquals('私', $textRun->getText()); + $textRun = $subElements[0]->getRubyTextRun(); + self::assertEquals('わたし', $textRun->getText()); + } + + /** + * Test reading of ruby title. + */ + public function testReadRubyTitle(): void + { + $documentXml = ' + + + + + + + + + + + + + + + + + + + かみ + + + + + + + + + + + + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Title', $elements[0]); + /** @var \PhpOffice\PhpWord\Element\Title $title */ + $title = $elements[0]; + /** @var \PhpOffice\PhpWord\Element\TextRun $textRun */ + $textRun = $title->getText(); + $subElements = $textRun->getElements(); // + self::assertInstanceOf('PhpOffice\PhpWord\Element\Ruby', $subElements[0]); + /** @var Ruby $ruby */ + $ruby = $subElements[0]; + /** @var RubyProperties $rubyProperties */ + $rubyProperties = $ruby->getProperties(); + self::assertEquals(RubyProperties::ALIGNMENT_DISTRIBUTE_SPACE, $rubyProperties->getAlignment()); + self::assertEquals(20, $rubyProperties->getFontFaceSize()); + self::assertEquals(38, $rubyProperties->getFontPointsAboveBaseText()); + self::assertEquals(40, $rubyProperties->getFontSizeForBaseText()); + self::assertEquals('ja-JP', $rubyProperties->getLanguageId()); + /** @var \PhpOffice\PhpWord\Element\TextRun $textRun */ + $textRun = $ruby->getBaseTextRun(); + self::assertEquals('神', $textRun->getText()); + $textRun = $ruby->getRubyTextRun(); + self::assertEquals('かみ', $textRun->getText()); + } +} diff --git a/tests/PhpWordTests/Reader/Word2007/PartTest.php b/tests/PhpWordTests/Reader/Word2007/PartTest.php new file mode 100644 index 0000000000..4f19f8b91f --- /dev/null +++ b/tests/PhpWordTests/Reader/Word2007/PartTest.php @@ -0,0 +1,236 @@ + + + This is a test + + + + + + + + + + + And another one + + + + + + + + '; + + $footnotesXml = ' + + + + + + + + + + + + + + + + + + + + + + footnote text + + + '; + + $endnotesXml = ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This is an endnote + + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml, 'footnotes' => $footnotesXml, 'endnotes' => $endnotesXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + /** @var \PhpOffice\PhpWord\Element\TextRun $textRun */ + $textRun = $elements[0]; + + //test the text in the first paragraph + /** @var \PhpOffice\PhpWord\Element\Text $text */ + $text = $elements[0]->getElement(0); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $text); + self::assertEquals('This is a test', $text->getText()); + + //test the presence of the footnote in the document.xml + /** @var \PhpOffice\PhpWord\Element\Footnote $footnote */ + $documentFootnote = $textRun->getElement(1); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Footnote', $documentFootnote); + self::assertEquals(1, $documentFootnote->getRelationId()); + + //test the presence of the footnote in the footnote.xml + /** @var \PhpOffice\PhpWord\Element\Footnote $footnote */ + $footnote = $phpWord->getFootnotes()->getItem(1); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Footnote', $footnote); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $footnote->getElement(0)); + self::assertEquals('footnote text', $footnote->getElement(0)->getText()); + self::assertEquals(1, $footnote->getRelationId()); + + //test the text in the second paragraph + /** @var \PhpOffice\PhpWord\Element\Text $text */ + $text = $elements[1]->getElement(0); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $text); + self::assertEquals('And another one', $text->getText()); + + //test the presence of the endnote in the document.xml + /** @var \PhpOffice\PhpWord\Element\Endnote $endnote */ + $documentEndnote = $elements[1]->getElement(1); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Endnote', $documentEndnote); + self::assertEquals(2, $documentEndnote->getRelationId()); + + //test the presence of the endnote in the endnote.xml + /** @var \PhpOffice\PhpWord\Element\Endnote $endnote */ + $endnote = $phpWord->getEndnotes()->getItem(1); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Endnote', $endnote); + self::assertEquals(2, $endnote->getRelationId()); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $endnote->getElement(0)); + self::assertEquals('This is an endnote', $endnote->getElement(0)->getText()); + } + + public function testReadHeadingWithOverriddenStyle(): void + { + $documentXml = ' + + + + + This is a bold + + + + + + heading + + + but with parts not in bold + + '; + + $stylesXml = ' + + + + + + + + + + + + + + + + + + + + + + + + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml, 'styles' => $stylesXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Title', $elements[0]); + /** @var \PhpOffice\PhpWord\Element\Title $title */ + $title = $elements[0]; + self::assertEquals('Heading1', $title->getStyle()); + + /** @var \PhpOffice\PhpWord\Element\Text $text */ + $text = $title->getText()->getElement(0); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $text); + self::assertEquals('This is a bold ', $text->getText()); + + /** @var \PhpOffice\PhpWord\Element\Text $text */ + $text = $title->getText()->getElement(1); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $text); + self::assertEquals('heading', $text->getText()); + self::assertFalse($text->getFontStyle()->isBold()); + + /** @var \PhpOffice\PhpWord\Element\Text $text */ + $text = $title->getText()->getElement(2); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $text); + self::assertEquals(' but with parts not in bold', $text->getText()); + } +} diff --git a/tests/PhpWordTests/Reader/Word2007/StyleTest.php b/tests/PhpWordTests/Reader/Word2007/StyleTest.php new file mode 100644 index 0000000000..7f4afde476 --- /dev/null +++ b/tests/PhpWordTests/Reader/Word2007/StyleTest.php @@ -0,0 +1,417 @@ + + + + + '; + + $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()); + self::assertEquals(Table::LAYOUT_FIXED, $elements[0]->getStyle()->getLayout()); + } + + /** + * Test reading of table position. + */ + public function testReadTablePosition(): void + { + $documentXml = ' + + + + '; + + $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()); + self::assertNotNull($elements[0]->getStyle()->getPosition()); + self::assertInstanceOf('PhpOffice\PhpWord\Style\TablePosition', $elements[0]->getStyle()->getPosition()); + /** @var TablePosition $tableStyle */ + $tableStyle = $elements[0]->getStyle()->getPosition(); + self::assertEquals(10, $tableStyle->getLeftFromText()); + self::assertEquals(20, $tableStyle->getRightFromText()); + self::assertEquals(30, $tableStyle->getTopFromText()); + self::assertEquals(40, $tableStyle->getBottomFromText()); + self::assertEquals(TablePosition::VANCHOR_PAGE, $tableStyle->getVertAnchor()); + self::assertEquals(TablePosition::HANCHOR_MARGIN, $tableStyle->getHorzAnchor()); + self::assertEquals(TablePosition::XALIGN_CENTER, $tableStyle->getTblpXSpec()); + self::assertEquals(50, $tableStyle->getTblpX()); + self::assertEquals(TablePosition::YALIGN_TOP, $tableStyle->getTblpYSpec()); + self::assertEquals(60, $tableStyle->getTblpY()); + } + + public function testReadTableCellNoWrap(): void + { + $documentXml = ' + + + + + + + + '; + + $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 = ' + + + + '; + + $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 Table $tableStyle */ + $tableStyle = $elements[0]->getStyle(); + self::assertEquals(TblWidth::AUTO, $tableStyle->getUnit()); + self::assertEquals(10.5, $tableStyle->getCellSpacing()); + } + + public function testReadTableCellStyle(): void + { + $documentXml = ' + + + + + + + + + + + + + + + + + '; + + $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 = ' + + + + + + + + + + + + + + + + + + '; + + $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. + */ + public function testReadPosition(): void + { + $documentXml = ' + + + + + This text is lowered + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + /** @var TextRun $elements */ + $textRun = $elements[0]; + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $textRun); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(0)); + self::assertInstanceOf('PhpOffice\PhpWord\Style\Font', $textRun->getElement(0)->getFontStyle()); + /** @var Style\Font $fontStyle */ + $fontStyle = $textRun->getElement(0)->getFontStyle(); + self::assertEquals(15, $fontStyle->getPosition()); + } + + public function testReadIndent(): void + { + $documentXml = ' + + + + '; + + $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 Table $tableStyle */ + $tableStyle = $elements[0]->getStyle(); + self::assertSame(TblWidth::TWIP, $tableStyle->getIndent()->getType()); + self::assertSame(2160, $tableStyle->getIndent()->getValue()); + } + + public function testReadTableRTL(): void + { + $documentXml = ' + + + + '; + + $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 Table $tableStyle */ + $tableStyle = $elements[0]->getStyle(); + self::assertTrue($tableStyle->isBidiVisual()); + } + + public function testReadHidden(): void + { + $documentXml = ' + + + + + This text is hidden + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + /** @var TextRun $elements */ + $textRun = $elements[0]; + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $textRun); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(0)); + self::assertInstanceOf('PhpOffice\PhpWord\Style\Font', $textRun->getElement(0)->getFontStyle()); + /** @var Style\Font $fontStyle */ + $fontStyle = $textRun->getElement(0)->getFontStyle(); + self::assertTrue($fontStyle->isHidden()); + } + + public function testReadHeading(): void + { + Style::resetStyles(); + + $documentXml = ' + + + + + + + + + + + + + '; + + $name = 'Heading_1'; + + $this->getDocumentFromString(['styles' => $documentXml]); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', Style::getStyle($name)); + } + + public function testPageVerticalAlign(): void + { + $documentXml = ' + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $sectionStyle = $phpWord->getSection(0)->getStyle(); + self::assertEquals(VerticalJc::CENTER, $sectionStyle->getVAlign()); + } + + /** + * @dataProvider providerIndentation + */ + public function testIndentation( + string $indent, + float $left, + float $right, + ?float $hanging, + float $firstLine, + int $firstLineChars + ): void { + $documentXml = " + + $indent + + + 1. + + "; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $section = $phpWord->getSection(0); + $textRun = $section->getElements()[0]; + self::assertInstanceOf(TextRun::class, $textRun); + + $paragraphStyle = $textRun->getParagraphStyle(); + self::assertInstanceOf(Style\Paragraph::class, $paragraphStyle); + + $indentation = $paragraphStyle->getIndentation(); + self::assertSame($left, $indentation->getLeft()); + self::assertSame($right, $indentation->getRight()); + self::assertSame($hanging, $indentation->getHanging()); + self::assertSame($firstLine, $indentation->getFirstLine()); + self::assertSame($firstLineChars, $indentation->getFirstLineChars()); + } + + /** + * @return Generator + */ + public static function providerIndentation() + { + yield [ + '', + 709.00, + 488.00, + 10.0, + 490.00, + 140, + ]; + yield [ + '', + 709.00, + 488.00, + 10.0, + 490.00, + 0, + ]; + yield [ + '', + 0, + 0, + 10.0, + 490.00, + 0, + ]; + yield [ + '', + 709.00, + 0, + 0, + 0, + 0, + ]; + yield [ + '', + 0, + 488.00, + 0, + 0, + 0, + ]; + } +} diff --git a/tests/PhpWordTests/Reader/Word2007Test.php b/tests/PhpWordTests/Reader/Word2007Test.php new file mode 100644 index 0000000000..65c8a4a71b --- /dev/null +++ b/tests/PhpWordTests/Reader/Word2007Test.php @@ -0,0 +1,264 @@ +canRead(dirname(__DIR__, 1) . '/_files/documents/reader.docx')); + } + + /** + * Can read exception. + */ + public function testCanReadFailed(): void + { + $object = new Word2007(); + self::assertFalse($object->canRead(dirname(__DIR__, 1) . '/_files/documents/foo.docx')); + } + + /** + * Load. + */ + public function testLoad(): void + { + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader.docx', 'Word2007'); + + self::assertInstanceOf(PhpWord::class, $phpWord); + self::assertTrue($phpWord->getSettings()->hasDoNotTrackMoves()); + self::assertFalse($phpWord->getSettings()->hasDoNotTrackFormatting()); + self::assertEquals(100, $phpWord->getSettings()->getZoom()); + + $doc = TestHelperDOCX::getDocument($phpWord); + 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); + + $element2e31 = $section2->getElement(31); + self::assertInstanceOf(TextRun::class, $element2e31); + self::assertEquals('This is a paragraph with border differents', $element2e31->getText()); + + /** @var Paragraph $element2e31pStyle */ + $element2e31pStyle = $element2e31->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $element2e31pStyle); + + // Top + self::assertEquals('FFFF00', $element2e31pStyle->getBorderTopColor()); + self::assertEquals('10', $element2e31pStyle->getBorderTopSize()); + self::assertEquals('dotted', $element2e31pStyle->getBorderTopStyle()); + // Right + self::assertEquals('00A933', $element2e31pStyle->getBorderRightColor()); + self::assertEquals('4', $element2e31pStyle->getBorderRightSize()); + self::assertEquals('dashed', $element2e31pStyle->getBorderRightStyle()); + // Bottom + self::assertEquals('F10D0C', $element2e31pStyle->getBorderBottomColor()); + self::assertEquals('8', $element2e31pStyle->getBorderBottomSize()); + self::assertEquals('dashSmallGap', $element2e31pStyle->getBorderBottomStyle()); + // Left + self::assertEquals('3465A4', $element2e31pStyle->getBorderLeftColor()); + self::assertEquals('8', $element2e31pStyle->getBorderLeftSize()); + self::assertEquals('dashed', $element2e31pStyle->getBorderLeftStyle()); + } + + /** + * Load a Word 2011 file. + */ + public function testLoadWord2011(): void + { + $reader = new Word2007(); + $phpWord = $reader->load(dirname(__DIR__, 1) . '/_files/documents/reader-2011.docx'); + + 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 static 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 new file mode 100644 index 0000000000..f8b9af661d --- /dev/null +++ b/tests/PhpWordTests/SettingsTest.php @@ -0,0 +1,332 @@ +compatibility = Settings::hasCompatibility(); + $this->defaultFontColor = Settings::getDefaultFontColor(); + $this->defaultFontSize = Settings::getDefaultFontSize(); + $this->defaultFontName = Settings::getDefaultFontName(); + $this->defaultPaper = Settings::getDefaultPaper(); + $this->measurementUnit = Settings::getMeasurementUnit(); + $this->outputEscapingEnabled = Settings::isOutputEscapingEnabled(); + $this->pdfRendererName = Settings::getPdfRendererName(); + $this->pdfRendererOptions = Settings::getPdfRendererOptions(); + $this->pdfRendererPath = Settings::getPdfRendererPath(); + $this->tempDir = Settings::getTempDir(); + $this->zipClass = Settings::getZipClass(); + $this->defaultRtl = Settings::isDefaultRtl(); + } + + protected function tearDown(): void + { + Settings::setCompatibility($this->compatibility); + Settings::setDefaultFontColor($this->defaultFontColor); + Settings::setDefaultFontSize($this->defaultFontSize); + Settings::setDefaultFontName($this->defaultFontName); + Settings::setDefaultPaper($this->defaultPaper); + Settings::setMeasurementUnit($this->measurementUnit); + Settings::setOutputEscapingEnabled($this->outputEscapingEnabled); + Settings::setPdfRendererName($this->pdfRendererName); + Settings::setPdfRendererOptions($this->pdfRendererOptions); + Settings::setPdfRendererPath($this->pdfRendererPath); + Settings::setTempDir($this->tempDir); + Settings::setZipClass($this->zipClass); + Settings::setDefaultRtl($this->defaultRtl); + } + + /** + * Test set/get compatibity option. + */ + public function testSetGetCompatibility(): void + { + self::assertTrue(Settings::hasCompatibility()); + self::assertTrue(Settings::setCompatibility(false)); + self::assertFalse(Settings::hasCompatibility()); + } + + /** + * Test set/get outputEscapingEnabled option. + */ + public function testSetGetOutputEscapingEnabled(): void + { + self::assertFalse(Settings::isOutputEscapingEnabled()); + Settings::setOutputEscapingEnabled(true); + 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. + */ + public function testSetGetZipClass(): void + { + self::assertEquals(Settings::ZIPARCHIVE, Settings::getZipClass()); + self::assertFalse(Settings::setZipClass('foo')); + self::assertEquals(Settings::ZIPARCHIVE, Settings::getZipClass()); + self::assertTrue(Settings::setZipClass(Settings::PCLZIP)); + self::assertEquals(Settings::getZipClass(), Settings::PCLZIP); + self::assertFalse(Settings::setZipClass('foo')); + self::assertEquals(Settings::getZipClass(), Settings::PCLZIP); + } + + /** + * Test set/get PDF renderer. + */ + public function testSetGetPdfRenderer(): void + { + $domPdfPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + + self::assertFalse(Settings::setPdfRenderer('FOO', 'dummy/path')); + self::assertTrue(Settings::setPdfRenderer(Settings::PDF_RENDERER_DOMPDF, $domPdfPath)); + self::assertEquals(Settings::PDF_RENDERER_DOMPDF, Settings::getPdfRendererName()); + self::assertEquals($domPdfPath, Settings::getPdfRendererPath()); + self::assertFalse(Settings::setPdfRendererPath('dummy/path')); + 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. + */ + public function testSetGetMeasurementUnit(): void + { + self::assertEquals(Settings::UNIT_TWIP, Settings::getMeasurementUnit()); + self::assertFalse(Settings::setMeasurementUnit('foo')); + self::assertEquals(Settings::UNIT_TWIP, Settings::getMeasurementUnit()); + self::assertTrue(Settings::setMeasurementUnit(Settings::UNIT_INCH)); + self::assertEquals(Settings::UNIT_INCH, Settings::getMeasurementUnit()); + self::assertFalse(Settings::setMeasurementUnit('foo')); + self::assertEquals(Settings::UNIT_INCH, Settings::getMeasurementUnit()); + } + + /** + * @covers ::getTempDir + */ + public function testPhpTempDirIsUsedByDefault(): void + { + self::assertEquals(sys_get_temp_dir(), Settings::getTempDir()); + } + + /** + * @covers ::getTempDir + * @covers ::setTempDir + * + * @depends testPhpTempDirIsUsedByDefault + */ + public function testTempDirCanBeSet(): void + { + $userDefinedTempDir = 'C:\PhpWordTemp'; + Settings::setTempDir($userDefinedTempDir); + $currentTempDir = Settings::getTempDir(); + self::assertEquals($userDefinedTempDir, $currentTempDir); + self::assertNotEquals(sys_get_temp_dir(), $currentTempDir); + } + + /** + * Test set/get default font name. + */ + public function testSetGetDefaultFontName(): void + { + self::assertEquals(Settings::DEFAULT_FONT_NAME, Settings::getDefaultFontName()); + self::assertFalse(Settings::setDefaultFontName(' ')); + self::assertEquals(Settings::DEFAULT_FONT_NAME, Settings::getDefaultFontName()); + self::assertTrue(Settings::setDefaultFontName('Times New Roman')); + self::assertEquals('Times New Roman', Settings::getDefaultFontName()); + self::assertFalse(Settings::setDefaultFontName(' ')); + self::assertEquals('Times New Roman', Settings::getDefaultFontName()); + } + + /** + * Test set/get default font name. + */ + public function testSetGetDefaultAsianFontName(): void + { + self::assertEquals(Settings::DEFAULT_FONT_NAME, Settings::getDefaultAsianFontName()); + self::assertFalse(Settings::setDefaultAsianFontName(' ')); + self::assertEquals(Settings::DEFAULT_FONT_NAME, Settings::getDefaultAsianFontName()); + self::assertTrue(Settings::setDefaultAsianFontName('Times New Roman')); + self::assertEquals('Times New Roman', Settings::getDefaultAsianFontName()); + self::assertFalse(Settings::setDefaultAsianFontName(' ')); + self::assertEquals('Times New Roman', Settings::getDefaultAsianFontName()); + } + + /** + * Test set/get default font size. + */ + public function testSetGetDefaultFontSize(): void + { + self::assertEquals(Settings::DEFAULT_FONT_SIZE, Settings::getDefaultFontSize()); + self::assertFalse(Settings::setDefaultFontSize(null)); + self::assertEquals(Settings::DEFAULT_FONT_SIZE, Settings::getDefaultFontSize()); + self::assertTrue(Settings::setDefaultFontSize(12)); + 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()); + } + + /** + * Test set/get default font color. + */ + public function testSetGetDefaultFontColor(): void + { + self::assertEquals(Settings::DEFAULT_FONT_COLOR, Settings::getDefaultFontColor()); + self::assertFalse(Settings::setDefaultFontColor(' ')); + self::assertEquals(Settings::DEFAULT_FONT_COLOR, Settings::getDefaultFontColor()); + self::assertTrue(Settings::setDefaultFontColor('FF0000')); + self::assertEquals('FF0000', Settings::getDefaultFontColor()); + self::assertFalse(Settings::setDefaultFontColor(' ')); + self::assertEquals('FF0000', Settings::getDefaultFontColor()); + } + + /** + * Test set/get default paper. + */ + public function testSetGetDefaultPaper(): void + { + $dflt = Settings::DEFAULT_PAPER; + $chng = 'A4'; + $doc = new PhpWord(); + self::assertEquals($dflt, Settings::getDefaultPaper()); + $sec1 = $doc->addSection(); + self::assertEquals($dflt, $sec1->getStyle()->getPaperSize()); + self::assertFalse(Settings::setDefaultPaper('')); + self::assertEquals($dflt, Settings::getDefaultPaper()); + self::assertTrue(Settings::setDefaultPaper($chng)); + self::assertEquals($chng, Settings::getDefaultPaper()); + $sec2 = $doc->addSection(); + self::assertEquals($chng, $sec2->getStyle()->getPaperSize()); + $sec3 = $doc->addSection(['paperSize' => 'Legal']); + self::assertEquals('Legal', $sec3->getStyle()->getPaperSize()); + self::assertFalse(Settings::setDefaultPaper('')); + self::assertEquals($chng, Settings::getDefaultPaper()); + } + + /** + * Test load config. + */ + public function testLoadConfig(): void + { + $expected = [ + 'compatibility' => true, + 'zipClass' => 'ZipArchive', + 'pdfRendererName' => 'DomPDF', + 'pdfRendererPath' => '', + 'defaultFontName' => 'Arial', + 'defaultFontSize' => 10, + 'defaultFontColor' => '000000', + 'outputEscapingEnabled' => false, + 'defaultPaper' => 'A4', + ]; + + // Test default value + self::assertEquals($expected, Settings::loadConfig()); + + // Test with valid file + self::assertEquals($expected, Settings::loadConfig(__DIR__ . '/../../phpword.ini.dist')); + foreach ($expected as $key => $value) { + if ($key === 'compatibility') { + $meth = 'hasCompatibility'; + } elseif ($key === 'outputEscapingEnabled') { + $meth = 'isOutputEscapingEnabled'; + } else { + $meth = 'get' . ucfirst($key); + } + self::assertEquals(Settings::$meth(), $value); + } + + // Test with invalid file + self::assertEmpty(Settings::loadConfig(__DIR__ . '/../../phpunit.xml.dist')); + } +} diff --git a/tests/PhpWordTests/Shared/ConverterTest.php b/tests/PhpWordTests/Shared/ConverterTest.php new file mode 100644 index 0000000000..36ba797aeb --- /dev/null +++ b/tests/PhpWordTests/Shared/ConverterTest.php @@ -0,0 +1,141 @@ +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/DrawingTest.php b/tests/PhpWordTests/Shared/DrawingTest.php new file mode 100644 index 0000000000..dbaa42a686 --- /dev/null +++ b/tests/PhpWordTests/Shared/DrawingTest.php @@ -0,0 +1,115 @@ +addSection(); + self::assertCount(0, $section->getElements()); + + // Heading + $styles = ['strong', 'em', 'sup', 'sub']; + for ($level = 1; $level <= 6; ++$level) { + $content .= "Heading {$level}"; + } + + // Styles + $content .= '

      '; + foreach ($styles as $style) { + $content .= "<{$style}>{$style}"; + } + $content .= '

      '; + + // Add HTML + Html::addHtml($section, $content); + self::assertCount(7, $section->getElements()); + + // Other parts + $section = $phpWord->addSection(); + $content = ''; + $content .= '
      HeaderContent
      '; + $content .= '
      • Bullet
        • Bullet
      '; + $content .= '
      1. Bullet
      '; + $content .= "'Single Quoted Text'"; + $content .= '"Double Quoted Text"'; + $content .= '& Ampersand'; + $content .= '<>“‘’«»‹›'; + $content .= '&•°…™©®—'; + $content .= '–   ²³¼½¾'; + Html::addHtml($section, $content); + } + + /** + * Test that html already in body element can be read. + * + * @ignore + */ + public function testParseFullHtml(): void + { + $section = new Section(1); + Html::addHtml($section, '

      test paragraph1

      test paragraph2

      ', true); + + self::assertCount(2, $section->getElements()); + } + + public function testParseHeader(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, '

      Text

      '); + + self::assertCount(1, $section->getElements()); + $element = $section->getElement(0); + self::assertInstanceOf(TextRun::class, $element); + self::assertInstanceOf(Paragraph::class, $element->getParagraphStyle()); + self::assertEquals('Heading1', $element->getParagraphStyle()->getStyleName()); + self::assertEquals('', $element->getParagraphStyle()->getAlignment()); + self::assertEquals('Text', $element->getText()); + self::assertCount(1, $element->getElements()); + $subElement = $element->getElement(0); + self::assertInstanceOf(Text::class, $subElement); + self::assertInstanceOf(Font::class, $subElement->getFontStyle()); + self::assertNull($subElement->getFontStyle()->getColor()); + self::assertEquals('Text', $subElement->getText()); + } + + public function testParseHeaderStyle(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, '

      Text

      '); + + self::assertCount(1, $section->getElements()); + $element = $section->getElement(0); + self::assertInstanceOf(TextRun::class, $element); + self::assertInstanceOf(Paragraph::class, $element->getParagraphStyle()); + self::assertEquals('Heading1', $element->getParagraphStyle()->getStyleName()); + self::assertEquals('center', $element->getParagraphStyle()->getAlignment()); + self::assertEquals('Text', $element->getText()); + self::assertCount(1, $element->getElements()); + $subElement = $element->getElement(0); + self::assertInstanceOf(Text::class, $subElement); + self::assertInstanceOf(Font::class, $subElement->getFontStyle()); + self::assertEquals('ff0000', $subElement->getFontStyle()->getColor()); + self::assertEquals('Text', $subElement->getText()); + } + + /** + * Test HTML entities. + */ + public function testParseHtmlEntities(): void + { + \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, 'text with entities <my text>'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r/w:t')); + self::assertEquals('text with entities ', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue); + } + + public function testParseStyle(): void + { + $html = ' + +

      Calculator

      '; + $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 = '
      '; + $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 text-decoration style. + */ + public function testParseTextDecoration(): void + { + $html = 'test'; + $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:u')); + self::assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:rPr/w:u', 'w:val')); + } + + /** + * Test underline. + */ + public function testParseUnderline(): void + { + $html = 'test'; + $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:u')); + self::assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:rPr/w:u', 'w:val')); + } + + /** + * Test width. + * + * @dataProvider providerParseWidth + */ + public function testParseWidth(string $htmlSize, float $docxSize, string $docxUnit): void + { + $html = '
      A
      '; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + $xpath = '/w:document/w:body/w:tbl/w:tblPr/w:tblW'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals($docxSize, $doc->getElement($xpath)->getAttribute('w:w')); + self::assertEquals($docxUnit, $doc->getElement($xpath)->getAttribute('w:type')); + } + + /** + * Test font-variant style. + */ + public function testParseFontVariant(): void + { + $html = 'test'; + $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. + */ + public function testParseFont(): void + { + $html = 'test'; + $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')); + //TODO check style + } + + /** + * Test line-height style. + */ + public function testParseLineHeight(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, '

      test

      '); + Html::addHtml($section, '

      test

      '); + Html::addHtml($section, '

      test

      '); + Html::addHtml($section, '

      test

      '); + Html::addHtml($section, '

      test

      '); + Html::addHtml($section, '

      test

      '); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:spacing')); + self::assertEquals(Paragraph::LINE_HEIGHT * 1.5, $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:spacing', 'w:line')); + self::assertEquals(LineSpacingRule::AUTO, $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:spacing', 'w:lineRule')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:pPr/w:spacing')); + self::assertEquals(300, $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:spacing', 'w:line')); + self::assertEquals(LineSpacingRule::EXACT, $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:spacing', 'w:lineRule')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[3]/w:pPr/w:spacing')); + self::assertEquals(Paragraph::LINE_HEIGHT * 1.2, $doc->getElementAttribute('/w:document/w:body/w:p[3]/w:pPr/w:spacing', 'w:line')); + self::assertEquals(LineSpacingRule::AUTO, $doc->getElementAttribute('/w:document/w:body/w:p[3]/w:pPr/w:spacing', 'w:lineRule')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[4]/w:pPr/w:spacing')); + self::assertEquals(244.8, $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:spacing', 'w:line')); + self::assertEquals(LineSpacingRule::EXACT, $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:spacing', 'w:lineRule')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[5]/w:pPr/w:spacing')); + self::assertEquals(Paragraph::LINE_HEIGHT, $doc->getElementAttribute('/w:document/w:body/w:p[5]/w:pPr/w:spacing', 'w:line')); + self::assertEquals(LineSpacingRule::AUTO, $doc->getElementAttribute('/w:document/w:body/w:p[5]/w:pPr/w:spacing', 'w:lineRule')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[6]/w:pPr/w:spacing')); + self::assertEquals(Paragraph::LINE_HEIGHT, $doc->getElementAttribute('/w:document/w:body/w:p[6]/w:pPr/w:spacing', 'w:line')); + self::assertEquals(LineSpacingRule::AUTO, $doc->getElementAttribute('/w:document/w:body/w:p[6]/w:pPr/w:spacing', 'w:lineRule')); + } + + public function testParseCellPaddingStyle(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $top = 10; + $right = 11; + $bottom = 12; + $left = 13; + + $testValTop = Converter::pixelToTwip($top); + $testValRight = Converter::pixelToTwip($right); + $testValBottom = Converter::pixelToTwip($bottom); + $testValLeft = Converter::pixelToTwip($left); + + $html = ' + + + + + + + + + +
      fullmixtopbottomleft
      '; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcMar/w:top'; + self::assertTrue($doc->elementExists($path)); + $path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcMar/w:bottom'; + self::assertTrue($doc->elementExists($path)); + $path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcMar/w:end'; + self::assertTrue($doc->elementExists($path)); + $path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcMar/w:start'; + self::assertTrue($doc->elementExists($path)); + + $path = '/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:tcPr/w:tcMar/w:end'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals($testValRight, $doc->getElementAttribute($path, 'w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type')); + + $path = '/w:document/w:body/w:tbl/w:tr/w:tc[3]/w:tcPr/w:tcMar/w:top'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals($testValTop, $doc->getElementAttribute($path, 'w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type')); + + $path = '/w:document/w:body/w:tbl/w:tr/w:tc[4]/w:tcPr/w:tcMar/w:bottom'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals($testValBottom, $doc->getElementAttribute($path, 'w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type')); + + $path = '/w:document/w:body/w:tbl/w:tr/w:tc[5]/w:tcPr/w:tcMar/w:start'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals($testValLeft, $doc->getElementAttribute($path, 'w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type')); + } + + /** + * Test text-indent style. + */ + public function testParseTextIndent(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, '

      test

      '); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:ind')); + self::assertEquals(750, $doc->getElementAttribute('/w:document/w:body/w:p/w:pPr/w:ind', 'w:firstLine')); + } + + /** + * Test text-align style. + */ + public function testParseTextAlign(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, '

      test

      '); + Html::addHtml($section, '

      test

      '); + Html::addHtml($section, '

      test

      '); + Html::addHtml($section, '

      test

      '); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:jc')); + self::assertEquals(Jc::START, $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:jc', 'w:val')); + self::assertEquals(Jc::END, $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:jc', 'w:val')); + self::assertEquals(Jc::CENTER, $doc->getElementAttribute('/w:document/w:body/w:p[3]/w:pPr/w:jc', 'w:val')); + self::assertEquals(Jc::BOTH, $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:jc', 'w:val')); + } + + /** + * Test font-size style. + */ + public function testParseFontSize(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, 'test'); + Html::addHtml($section, 'test'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:sz')); + self::assertEquals('20', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:r/w:rPr/w:sz', 'w:val')); + self::assertEquals('15', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:r/w:rPr/w:sz', 'w:val')); + } + + /** + * Test direction style. + */ + public function testParseTextDirection(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, 'test'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:rtl')); + } + + /** + * Test html lang. + */ + public function testParseLang(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, 'test'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:lang')); + self::assertEquals('fr-BE', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:rPr/w:lang', 'w:val')); + } + + /** + * Test font-family style. + */ + public function testParseFontFamily(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, 'test'); + Html::addHtml($section, 'test'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:rFonts')); + self::assertEquals('Arial', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:r/w:rPr/w:rFonts', 'w:ascii')); + self::assertEquals('Times New Roman', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:r/w:rPr/w:rFonts', 'w:ascii')); + } + + /** + * Test parsing paragraph and span styles. + */ + public function testParseParagraphAndSpanStyle(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, '

      test

      '); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:jc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:spacing')); + self::assertEquals(Jc::CENTER, $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:jc', 'w:val')); + self::assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:r/w:rPr/w:u', 'w:val')); + } + + /** + * Test parsing paragraph with `page-break-after` style. + */ + public function testParseParagraphWithPageBreak(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, '

      '); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:br')); + self::assertEquals('page', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:br', 'w:type')); + } + + /** + * Test parsing table. + */ + public function testParseTable(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = ' + + + + + + + + + + + +

      header a

      header bheader c
      12
      This is bold text5

      6

      '; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr/w:jc')); + self::assertEquals(Jc::START, $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tblPr/w:jc', 'w:val')); + + //check border colors + self::assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:top', 'w:color')); + self::assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:right', 'w:color')); + self::assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:bottom', 'w:color')); + self::assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:left', 'w:color')); + + self::assertEquals('00AA00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:top', 'w:color')); + self::assertEquals('00BB00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:right', 'w:color')); + self::assertEquals('00CC00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:bottom', 'w:color')); + self::assertEquals('00DD00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:left', 'w:color')); + + //check borders are not propagated inside cells + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[1]/w:p')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[1]/w:p/w:pPr/w:pBdr')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:p')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:p/w:pPr/w:pBdr')); + } + + /** + * Parse widths in tables and cells, which also allows for controlling column width. + */ + public function testParseTableAndCellWidth(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection([ + 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + ]); + + // borders & backgrounds are here just for better visual comparison + $html = << +
    25% + + + + + + + + + + + + + +
    400px
    T2.R2.C150ptT2.R2.C3
    300pxT2.R3.C3
    +
    +HTML; + + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + // outer table grid + $xpath = '/w:document/w:body/w:tbl/w:tblGrid/w:gridCol'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals(25 * 50, $doc->getElement($xpath)->getAttribute('w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElement($xpath)->getAttribute('w:type')); + + //
    elementExists($xpath)); + self::assertEquals(6000, $doc->getElement($xpath)->getAttribute('w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElement($xpath)->getAttribute('w:type')); + + // elementExists($xpath)); + self::assertEquals(4500, $doc->getElement($xpath)->getAttribute('w:w')); + self::assertEquals(TblWidth::TWIP, $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 = << +
    100px
    200pt
    + + + + +
    300px
    +
    +HTML; + + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + // elementExists($xpath)); + self::assertEquals(4000, $doc->getElement($xpath)->getAttribute('w:val')); + self::assertEquals('exact', $doc->getElement($xpath)->getAttribute('w:hRule')); + + // + + + Header + + + + Cell 1 + Cell 2 + + '; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr/w:tblBorders')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr/w:tblBorders/w:top')); + // 10 pixels = 150 twips + self::assertEquals(150, $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tblPr/w:tblBorders/w:top', 'w:sz')); + } + + /** + * Test parsing background color for table rows and table cellspacing. + */ + public function testParseTableCellspacingRowBgColor(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection([ + 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + ]); + + // borders & backgrounds are here just for better visual comparison + $html = << + + A + B + + + C + D + + +HTML; + + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $xpath = '/w:document/w:body/w:tbl/w:tblPr/w:tblCellSpacing'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals(3 * 15, $doc->getElement($xpath)->getAttribute('w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElement($xpath)->getAttribute('w:type')); + + $xpath = '/w:document/w:body/w:tbl/w:tr[1]/w:tc[1]/w:tcPr/w:shd'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals('lightgreen', $doc->getElement($xpath)->getAttribute('w:fill')); + + $xpath = '/w:document/w:body/w:tbl/w:tr[2]/w:tc[1]/w:tcPr/w:shd'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals('FF0000', $doc->getElement($xpath)->getAttribute('w:fill')); + } + + /** + * Test parsing background color for table rows and table cellspacing. + */ + public function testParseTableStyleAttributeInlineStyle(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection([ + 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + ]); + + $html = ' + + + +
    A
    '; + + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $xpath = '/w:document/w:body/w:tbl/w:tblPr/w:tblW'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals(100 * 50, $doc->getElement($xpath)->getAttribute('w:w')); + self::assertEquals(TblWidth::PERCENT, $doc->getElement($xpath)->getAttribute('w:type')); + + $xpath = '/w:document/w:body/w:tbl/w:tr[1]/w:tc[1]/w:tcPr/w:shd'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals('red', $doc->getElement($xpath)->getAttribute('w:fill')); + } + + /** + * Tests parsing of ul/li. + */ + public function testParseList(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '
      +
    • + + list item1 + +
    • +
    • + + list item2 + +
    • +
    '; + Html::addHtml($section, $html, false, false); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:numPr/w:numId')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t')); + self::assertEquals('list item1', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue); + self::assertEquals('list item2', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->nodeValue); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:b')); + } + + /** + * Tests parsing of ul/li. + */ + public function testOrderedListNumbering(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '
      +
    1. List 1 item 1
    2. +
    3. List 1 item 2
    4. +
    +

    Some Text

    +
      +
    1. List 2 item 1
    2. +
    3. List 2 item 2
    4. +
    '; + Html::addHtml($section, $html, false, false); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:numPr/w:numId')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t')); + + self::assertEquals('List 1 item 1', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue); + self::assertEquals('List 2 item 1', $doc->getElement('/w:document/w:body/w:p[4]/w:r/w:t')->nodeValue); + + $firstListnumId = $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:numPr/w:numId', 'w:val'); + $secondListnumId = $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:numPr/w:numId', 'w:val'); + + self::assertNotEquals($firstListnumId, $secondListnumId); + } + + /** + * Tests parsing of nested ul/li. + */ + public function testOrderedNestedListNumbering(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '
      +
    1. List 1 item 1
    2. +
    3. List 1 item 2
    4. +
    +

    Some Text

    +
      +
    1. List 2 item 1
    2. +
    3. +
        +
      1. sub list 1
      2. +
      3. sub list 2
      4. +
      +
    4. +
    '; + Html::addHtml($section, $html, false, false); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:numPr/w:numId')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t')); + + self::assertEquals('List 1 item 1', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue); + self::assertEquals('List 2 item 1', $doc->getElement('/w:document/w:body/w:p[4]/w:r/w:t')->nodeValue); + + $firstListnumId = $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:numPr/w:numId', 'w:val'); + $secondListnumId = $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:numPr/w:numId', 'w:val'); + + self::assertNotEquals($firstListnumId, $secondListnumId); + } + + /** + * Tests parsing of ul/li. + */ + public function testParseListWithFormat(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = preg_replace('/\s+/', ' ', '
      +
    • Some text before + + list item1 bold with text after bold + + and some after +
    • +
    • + + list item2 + +
    • +
    '); + Html::addHtml($section, $html, false, false); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:numPr/w:numId')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t')); + self::assertEquals('list item2', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->nodeValue); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r[3]/w:rPr/w:b')); + self::assertEquals('bold', $doc->getElement('/w:document/w:body/w:p[1]/w:r[3]/w:t')->nodeValue); + } + + /** + * Tests parsing of br. + */ + public function testParseLineBreak(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

    This is some text
    with a linebreak.

    '; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:br')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t')); + self::assertEquals('This is some text', $doc->getElement('/w:document/w:body/w:p/w:r[1]/w:t')->nodeValue); + self::assertEquals('with a linebreak.', $doc->getElement('/w:document/w:body/w:p/w:r[2]/w:t')->nodeValue); + } + + /** + * Test parsing of img. + */ + public function testParseImage(): void + { + $src = __DIR__ . '/../_files/images/firefox.png'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

    '; + 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')); + self::assertStringMatchesFormat('%Smso-position-horizontal:right%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + 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 = '

    '; + 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 = '

    '; + 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 = '

    '; + 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. + */ + public function testParseRemoteImage(): void + { + $src = self::getRemoteImageUrl(); + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

    '; + 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 of remote img without extension. + */ + public function testParseRemoteImageWithoutExtension(): void + { + $src = self::getRemoteImageUrlWithoutExtension(); + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

    '; + 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. + */ + public function testParseEmbeddedImage(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

    '; + 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 of remote img that can be found locally. + */ + public function testParseRemoteLocalImage(): void + { + $src = 'https://fakedomain.io/images/firefox.png'; + $localPath = __DIR__ . '/../_files/images/'; + $options = [ + 'IMG_SRC_SEARCH' => 'https://fakedomain.io/images/', + 'IMG_SRC_REPLACE' => $localPath, + ]; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

    '; + Html::addHtml($section, $html, false, true, $options); + + $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 of remote img that can be found locally. + */ + public function testCouldNotLoadImage(): void + { + $this->expectException(Exception::class); + $src = 'https://fakedomain.io/images/firefox.png'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

    '; + Html::addHtml($section, $html, false, true); + } + + public function testParseLink(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

    link text

    '; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink')); + 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'); + $html = '

    internal link text

    '; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink')); + self::assertTrue($doc->getElement('/w:document/w:body/w:p/w:hyperlink')->hasAttribute('w:anchor')); + self::assertEquals('bookmark', $doc->getElement('/w:document/w:body/w:p/w:hyperlink')->getAttribute('w:anchor')); + } + + public function testParseLinkAllowsAbsenceOfHref(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

    text of href-less link

    '; + 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 PhpWord(); + $section = $phpWord->addSection(); + $html = '

    text of empty-href link

    '; + 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(); + $section = $phpWord->addSection(); + $html = '

    text

    '; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertFalse($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:jc')); + } + + /** + * Tests parsing hidden text. + */ + public function testParseHiddenText(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

    This is some hidden text.

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

    This is some text with letter spacing.

    '; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:spacing')); + self::assertEquals(150 * 15, $doc->getElement('/w:document/w:body/w:p/w:r/w:rPr/w:spacing')->getAttribute('w:val')); + } + + /** + * Tests checkbox input field. + */ + public function testInputCheckbox(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = ''; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r/w:fldChar/w:ffData/w:checkBox')); + self::assertEquals(1, $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:fldChar/w:ffData/w:checkBox/w:checked')->getAttribute('w:val')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:fldChar/w:ffData/w:checkBox')); + self::assertEquals(0, $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:fldChar/w:ffData/w:checkBox/w:checked')->getAttribute('w:val')); + } + + /** + * Parse horizontal rule. + */ + public function testParseHorizontalRule(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + // borders & backgrounds are here just for better visual comparison + $html = <<Simple default rule:

    +
    +

    Custom style rule:

    +
    +

    END

    +HTML; + + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + // default rule + $xpath = '/w:document/w:body/w:p[2]/w:pPr/w:pBdr/w:bottom'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals('single', $doc->getElement($xpath)->getAttribute('w:val')); // solid + self::assertEquals('1', $doc->getElement($xpath)->getAttribute('w:sz')); // 1 twip + self::assertEquals('000000', $doc->getElement($xpath)->getAttribute('w:color')); // black + + // custom style rule + $xpath = '/w:document/w:body/w:p[4]/w:pPr/w:pBdr/w:bottom'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals('single', $doc->getElement($xpath)->getAttribute('w:val')); + self::assertEquals((int) (5 * 15 / 2), $doc->getElement($xpath)->getAttribute('w:sz')); + self::assertEquals('lightblue', $doc->getElement($xpath)->getAttribute('w:color')); + + $xpath = '/w:document/w:body/w:p[4]/w:pPr/w:spacing'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals(450, $doc->getElement($xpath)->getAttribute('w:before')); + self::assertEquals(0, $doc->getElement($xpath)->getAttribute('w:after')); + self::assertEquals(240, $doc->getElement($xpath)->getAttribute('w:line')); + } + + /** + * Parse ordered list start & numbering style. + */ + public function testParseOrderedList(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + // borders & backgrounds are here just for better visual comparison + $html = << +
  • standard ordered list line 1
  • +
  • standard ordered list line 2
  • + + +
      +
    1. ordered list alphabetical, line 5 => E
    2. +
    3. ordered list alphabetical, line 6 => F
    4. +
    + +
      +
    1. ordered list roman lower, line 3 => iii
    2. +
    3. ordered list roman lower, line 4 => iv
    4. +
    + +HTML; + + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + // compare numbering file + $xmlFile = 'word/numbering.xml'; + + // default - decimal start = 1 + $xpath = '/w:numbering/w:abstractNum[1]/w:lvl[1]/w:start'; + self::assertTrue($doc->elementExists($xpath, $xmlFile)); + self::assertEquals('1', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val')); + + $xpath = '/w:numbering/w:abstractNum[1]/w:lvl[1]/w:numFmt'; + self::assertTrue($doc->elementExists($xpath, $xmlFile)); + self::assertEquals('decimal', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val')); + + // second list - start = 5, type A = upperLetter + $xpath = '/w:numbering/w:abstractNum[2]/w:lvl[1]/w:start'; + self::assertTrue($doc->elementExists($xpath, $xmlFile)); + self::assertEquals('5', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val')); + + $xpath = '/w:numbering/w:abstractNum[2]/w:lvl[1]/w:numFmt'; + self::assertTrue($doc->elementExists($xpath, $xmlFile)); + self::assertEquals('upperLetter', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val')); + + // third list - start = 3, type i = lowerRoman + $xpath = '/w:numbering/w:abstractNum[3]/w:lvl[1]/w:start'; + self::assertTrue($doc->elementExists($xpath, $xmlFile)); + self::assertEquals('3', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val')); + + $xpath = '/w:numbering/w:abstractNum[3]/w:lvl[1]/w:numFmt'; + self::assertTrue($doc->elementExists($xpath, $xmlFile)); + self::assertEquals('lowerRoman', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val')); + } + + /** + * Parse ordered list start & numbering style. + */ + public function testParseVerticalAlign(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + // borders & backgrounds are here just for better visual comparison + $html = << + + default + top + middle + bottom +






    + + +HTML; + + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:vAlign'; + self::assertFalse($doc->elementExists($xpath)); + + $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:tcPr/w:vAlign'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals('top', $doc->getElement($xpath)->getAttribute('w:val')); + + $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc[3]/w:tcPr/w:vAlign'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals('center', $doc->getElement($xpath)->getAttribute('w:val')); + + $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc[4]/w:tcPr/w:vAlign'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals('bottom', $doc->getElement($xpath)->getAttribute('w:val')); + } + + /** + * Fix bug - don't decode double quotes inside double quoted string. + */ + public function testDontDecodeAlreadyEncodedDoubleQuotes(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + // borders & backgrounds are here just for better visual comparison + $html = <<This would crash if inline quotes also decoded at loading XML into DOMDocument! +HTML; + + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertIsObject($doc); + } + + public static function providerParseWidth(): array + { + return [ + ['auto', 5000, TblWidth::PERCENT], + ['100%', 5000, TblWidth::PERCENT], + ['200pt', 3999.999999999999, TblWidth::TWIP], + ['300px', 4500, TblWidth::TWIP], + ['400', 6000, TblWidth::TWIP], + ]; + } + + /** + * Test ruby. + */ + public function testParseRubyHtml(): void + { + $html = << + base text + ( + ruby text + ) + +HTML; + $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:ruby')); + self::assertEquals('ruby text', $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rt/w:r/w:t')->textContent); + self::assertEquals( + 'base text', + $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rubyBase/w:r/w:t')->textContent + ); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:rubyAlign')); + self::assertEquals( + RubyProperties::ALIGNMENT_CENTER, + $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:rubyAlign', 'w:val') + ); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hps')); + self::assertEquals( + 10, + $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hps', 'w:val') + ); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hpsBaseText')); + self::assertEquals( + 20, + $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hpsBaseText', 'w:val') + ); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:lid')); + self::assertEquals( + 'en-US', + $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:lid', 'w:val') + ); + } +} diff --git a/tests/PhpWordTests/Shared/Microsoft/PasswordEncoderTest.php b/tests/PhpWordTests/Shared/Microsoft/PasswordEncoderTest.php new file mode 100644 index 0000000000..d3c13bf8b4 --- /dev/null +++ b/tests/PhpWordTests/Shared/Microsoft/PasswordEncoderTest.php @@ -0,0 +1,90 @@ +getDomFromString('AAA'); + + self::assertTrue($reader->elementExists('/element/child')); + self::assertEquals('AAA', $reader->getElement('/element/child')->textContent); + self::assertEquals('AAA', $reader->getValue('/element/child')); + self::assertEquals('test', $reader->getAttribute('attr', $reader->getElement('/element'))); + self::assertEquals('subtest', $reader->getAttribute('attr', $reader->getElement('/element'), 'child')); + } + + /** + * Test reading XML from zip. + */ + public function testDomFromZip(): 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')); + } + + /** + * 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. + */ + public function testThrowsExceptionOnNonExistingArchive(): void + { + $this->expectException(Exception::class); + $archiveFile = __DIR__ . '/../_files/xml/readers.zip'; + + $reader = new XMLReader(); + $reader->getDomFromZip($archiveFile, 'test.xml'); + } + + /** + * Test that read from invalid archive throws exception. + */ + public function testThrowsExceptionOnZipArchiveOpenErrors(): void + { + $tempPath = tempnam(sys_get_temp_dir(), 'PhpWord') ?: 'tempNameFile'; + + // 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. + */ + public function testCountElements(): void + { + $reader = new XMLReader(); + $reader->getDomFromString('AAABBB'); + + self::assertEquals(2, $reader->countElements('/element/child')); + } + + /** + * Test read non existing elements. + */ + public function testReturnNullOnNonExistingNode(): void + { + $reader = new XMLReader(); + self::assertSame(0, $reader->getElements('/element/children')->length); + $reader->getDomFromString('AAA'); + + self::assertNull($reader->getElement('/element/children')); + self::assertNull($reader->getValue('/element/children')); + } + + /** + * Test that xpath fails if custom namespace is not registered. + */ + public function testShouldThrowExceptionIfNamespaceIsNotKnown(): void + { + try { + $reader = new XMLReader(); + $reader->getDomFromString('AAA'); + + self::assertTrue($reader->elementExists('/element/test:child')); + self::assertEquals('AAA', $reader->getElement('/element/test:child')->textContent); + self::fail(); + } catch (Exception $e) { + // @phpstan-ignore-next-line + self::assertTrue(true); + } + } + + /** + * Test reading XML with manually registered namespace. + */ + public function testShouldParseXmlWithCustomNamespace(): void + { + $reader = new XMLReader(); + $reader->getDomFromString('AAA'); + $reader->registerNamespace('test', 'http://phpword.com/my/custom/namespace'); + + self::assertTrue($reader->elementExists('/element/test:child')); + self::assertEquals('AAA', $reader->getElement('/element/test:child')->textContent); + } + + /** + * Test that xpath fails if custom namespace is not registered. + */ + public function testShouldThowExceptionIfTryingToRegisterNamespaceBeforeReadingDoc(): void + { + $this->expectException(InvalidArgumentException::class); + $reader = new XMLReader(); + $reader->registerNamespace('test', 'http://phpword.com/my/custom/namespace'); + } +} diff --git a/tests/PhpWordTests/Shared/XMLWriterTest.php b/tests/PhpWordTests/Shared/XMLWriterTest.php new file mode 100644 index 0000000000..476b8b3ba9 --- /dev/null +++ b/tests/PhpWordTests/Shared/XMLWriterTest.php @@ -0,0 +1,74 @@ +startElement('element'); + $object->text('AAA'); + $object->endElement(); + self::assertEquals('AAA' . chr(10), $object->getData()); + + // Disk + $object = new XMLWriter(XMLWriter::STORAGE_DISK); + $object->startElement('element'); + $object->text('BBB'); + $object->endElement(); + self::assertEquals('BBB' . chr(10), $object->getData()); + } + + public function testWriteAttribute(): void + { + $xmlWriter = new XMLWriter(); + $xmlWriter->startElement('element'); + $xmlWriter->writeAttribute('name', 'value'); + $xmlWriter->endElement(); + + self::assertSame('' . chr(10), $xmlWriter->getData()); + } + + public function testWriteAttributeShouldWriteFloatValueLocaleIndependent(): void + { + $value = 1.2; + + $xmlWriter = new XMLWriter(); + $xmlWriter->startElement('element'); + $xmlWriter->writeAttribute('name', $value); + $xmlWriter->endElement(); + + $currentLocale = setlocale(LC_NUMERIC, 0); + + setlocale(LC_NUMERIC, 'de_DE.UTF-8', 'de'); + + self::assertSame('' . chr(10), $xmlWriter->getData()); + + setlocale(LC_NUMERIC, $currentLocale); + } +} diff --git a/tests/PhpWordTests/Shared/ZipArchiveTest.php b/tests/PhpWordTests/Shared/ZipArchiveTest.php new file mode 100644 index 0000000000..3f998c26ef --- /dev/null +++ b/tests/PhpWordTests/Shared/ZipArchiveTest.php @@ -0,0 +1,134 @@ +open($zipFile, ZipArchive::CREATE); +// $object->addFromString('content/string.txt', 'Test'); + +// // Lock the file +// $resource = fopen($zipFile, "w"); +// flock($resource, LOCK_EX); + +// // Closing the file should throws an exception +// $object->close(); + +// // Unlock the file +// flock($resource, LOCK_UN); +// fclose($resource); + +// @unlink($zipFile); +// } + + /** + * Test all methods. + * + * @param string $zipClass + */ + public function testZipArchive($zipClass = 'ZipArchive'): void + { + // Preparation + $existingFile = __DIR__ . '/../_files/documents/sheet.xls'; + $zipFile = __DIR__ . '/../_files/documents/ziptest.zip'; + $destination1 = __DIR__ . '/../_files/documents/extract1'; + $destination2 = __DIR__ . '/../_files/documents/extract2'; + @mkdir($destination1); + @mkdir($destination2); + + Settings::setZipClass($zipClass); + + $object = new ZipArchive(); + $object->open($zipFile, ZipArchive::CREATE); + $object->addFile($existingFile, 'xls/new.xls'); + $object->addFromString('content/string.txt', 'Test'); + $object->close(); + $object->open($zipFile); + + // Run tests + self::assertEquals(0, $object->locateName('xls/new.xls')); + self::assertFalse($object->locateName('blablabla')); + + self::assertEquals('Test', $object->getFromName('content/string.txt')); + self::assertEquals('Test', $object->getFromName('/content/string.txt')); + + self::assertFalse($object->getNameIndex(-1)); + self::assertEquals('content/string.txt', $object->getNameIndex(1)); + + self::assertFalse($object->extractTo('blablabla')); + self::assertTrue($object->extractTo($destination1)); + self::assertTrue($object->extractTo($destination2, 'xls/new.xls')); + self::assertFalse($object->extractTo($destination2, 'blablabla')); + + // Cleanup + $this->deleteDir($destination1); + $this->deleteDir($destination2); + @unlink($zipFile); + } + + /** + * Test PclZip. + */ + public function testPCLZip(): void + { + $this->testZipArchive('PhpOffice\PhpWord\Shared\ZipArchive'); + } + + /** + * Delete directory. + * + * @param string $dir + */ + private function deleteDir($dir): void + { + foreach (scandir($dir) as $file) { + if ('.' === $file || '..' === $file) { + continue; + } elseif (is_file($dir . '/' . $file)) { + unlink($dir . '/' . $file); + } elseif (is_dir($dir . '/' . $file)) { + $this->deleteDir($dir . '/' . $file); + } + } + + rmdir($dir); + } +} diff --git a/tests/PhpWordTests/Style/AbstractStyleTest.php b/tests/PhpWordTests/Style/AbstractStyleTest.php new file mode 100644 index 0000000000..679c9f5e49 --- /dev/null +++ b/tests/PhpWordTests/Style/AbstractStyleTest.php @@ -0,0 +1,141 @@ +getMockForAbstractClass(AbstractStyle::class); + } else { + /** @var AbstractStyle $stub */ + $stub = new class() extends AbstractStyle { + }; + } + $stub->setStyleByArray(['index' => 1]); + + self::assertEquals(1, $stub->getIndex()); + } + + public function testSetStyleByArrayWithAlign(): void + { + $stub = new Paragraph(); + $stub->setStyleByArray(['align' => Jc::CENTER]); + + self::assertEquals(Jc::CENTER, $stub->getAlignment()); + } + + public function testSetStyleByArrayWithAlignment(): void + { + $stub = new Paragraph(); + $stub->setStyleByArray(['alignment' => Jc::CENTER]); + + self::assertEquals(Jc::CENTER, $stub->getAlignment()); + } + + /** + * Test setBoolVal, setIntVal, setFloatVal, setEnumVal with normal value. + */ + public function testSetValNormal(): void + { + // @phpstan-ignore-next-line + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(AbstractStyle::class); + } else { + /** @var AbstractStyle $stub */ + $stub = new class() extends AbstractStyle { + }; + } + + self::assertTrue(self::callProtectedMethod($stub, 'setBoolVal', [true, false])); + self::assertEquals(12, self::callProtectedMethod($stub, 'setIntVal', [12, 200])); + self::assertEquals(871.1, self::callProtectedMethod($stub, 'setFloatVal', [871.1, 2.1])); + self::assertEquals(871.1, self::callProtectedMethod($stub, 'setFloatVal', ['871.1', 2.1])); + self::assertEquals('a', self::callProtectedMethod($stub, 'setEnumVal', ['a', ['a', 'b'], 'b'])); + } + + /** + * Test setBoolVal, setIntVal, setFloatVal, setEnumVal with default value. + */ + public function testSetValDefault(): void + { + // @phpstan-ignore-next-line + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(AbstractStyle::class); + } else { + /** @var AbstractStyle $stub */ + $stub = new class() extends AbstractStyle { + }; + } + + self::assertNotTrue(self::callProtectedMethod($stub, 'setBoolVal', ['a', false])); + self::assertEquals(200, self::callProtectedMethod($stub, 'setIntVal', ['foo', 200])); + self::assertEquals(2.1, self::callProtectedMethod($stub, 'setFloatVal', ['foo', 2.1])); + self::assertEquals('b', self::callProtectedMethod($stub, 'setEnumVal', [null, ['a', 'b'], 'b'])); + } + + /** + * Test setEnumVal exception. + */ + public function testSetValEnumException(): void + { + $this->expectException(InvalidArgumentException::class); + // @phpstan-ignore-next-line + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(AbstractStyle::class); + } else { + /** @var AbstractStyle $stub */ + $stub = new class() extends AbstractStyle { + }; + } + + self::assertEquals('b', self::callProtectedMethod($stub, 'setEnumVal', ['z', ['a', 'b'], 'b'])); + } + + /** + * Helper function to call protected method. + * + * @param mixed $object + * @param string $method + */ + public static function callProtectedMethod($object, $method, array $args = []) + { + $class = new ReflectionClass(get_class($object)); + $method = $class->getMethod($method); + $method->setAccessible(true); + + return $method->invokeArgs($object, $args); + } +} diff --git a/tests/PhpWordTests/Style/CellTest.php b/tests/PhpWordTests/Style/CellTest.php new file mode 100644 index 0000000000..56b099c05f --- /dev/null +++ b/tests/PhpWordTests/Style/CellTest.php @@ -0,0 +1,127 @@ + 120, + 'borderLeftSize' => 120, + 'borderRightSize' => 120, + 'borderBottomSize' => 120, + 'gridSpan' => 2, + ] as $key => $value) { + $set = "set{$key}"; + $get = "get{$key}"; + + self::assertNull($object->$get()); // Init with null value + + $object->$set($value); + + self::assertEquals($value, $object->$get()); + } + } + + public function testSetGetNormalString(): void + { + $object = new Cell(); + + foreach ([ + 'valign' => VerticalJc::TOP, + 'textDirection' => Cell::TEXT_DIR_BTLR, + 'bgColor' => 'FFFF00', + 'borderTopColor' => 'FFFF00', + 'borderLeftColor' => 'FFFF00', + 'borderRightColor' => 'FFFF00', + 'borderBottomColor' => 'FFFF00', + 'vMerge' => Cell::VMERGE_RESTART, + ] as $key => $value) { + $set = "set{$key}"; + $get = "get{$key}"; + + self::assertNull($object->$get()); // Init with null value + + $object->$set($value); + + self::assertEquals($value, $object->$get()); + } + } + + /** + * Test border color. + */ + public function testBorderColor(): void + { + $object = new Cell(); + + $value = 'FF0000'; + + $object->setStyleValue('borderColor', $value); + $expected = [$value, $value, $value, $value]; + self::assertEquals($expected, $object->getBorderColor()); + } + + /** + * Test border size. + */ + public function testBorderSize(): void + { + $object = new Cell(); + + $value = 120; + $expected = [$value, $value, $value, $value]; + $object->setStyleValue('borderSize', $value); + self::assertEquals($expected, $object->getBorderSize()); + } + + /** + * Test cell padding. + */ + public function testPadding(): void + { + $object = new Cell(); + $methods = [ + 'paddingTop' => 10, + 'paddingBottom' => 20, + 'paddingLeft' => 30, + 'paddingRight' => 40, + ]; + + foreach ($methods as $methodName => $methodValue) { + $object->setStyleValue($methodName, $methodValue); + $getterName = 'get' . ucfirst($methodName); + + self::assertEquals($methodValue, $object->$getterName()); + } + } +} diff --git a/tests/PhpWordTests/Style/ChartTest.php b/tests/PhpWordTests/Style/ChartTest.php new file mode 100644 index 0000000000..e8e75901d7 --- /dev/null +++ b/tests/PhpWordTests/Style/ChartTest.php @@ -0,0 +1,188 @@ +getWidth(), 1000000); + + $chart->setWidth(200); + + self::assertEquals($chart->getWidth(), 200); + } + + /** + * Testing getter and setter for chart height. + */ + public function testSetGetHeight(): void + { + $chart = new Chart(); + + self::assertEquals($chart->getHeight(), 1000000); + + $chart->setHeight(200); + + self::assertEquals($chart->getHeight(), 200); + } + + /** + * Testing getter and setter for is3d. + */ + public function testSetIs3d(): void + { + $chart = new Chart(); + + self::assertEquals($chart->is3d(), false); + + $chart->set3d(true); + + self::assertEquals($chart->is3d(), true); + } + + /** + * Testing getter and setter for chart colors. + */ + public function testSetGetColors(): void + { + $chart = new Chart(); + + self::assertIsArray($chart->getColors()); + + self::assertEquals(count($chart->getColors()), 0); + + $chart->setColors(['FFFFFFFF', 'FF000000', 'FFFF0000']); + + self::assertEquals($chart->getColors(), ['FFFFFFFF', 'FF000000', 'FFFF0000']); + } + + /** + * Testing getter and setter for dataLabelOptions. + */ + public function testSetGetDataLabelOptions(): void + { + $chart = new Chart(); + + $originalDataLabelOptions = [ + 'showVal' => true, + 'showCatName' => true, + 'showLegendKey' => false, + 'showSerName' => false, + 'showPercent' => false, + 'showLeaderLines' => false, + 'showBubbleSize' => false, + ]; + + self::assertEquals($chart->getDataLabelOptions(), $originalDataLabelOptions); + + $changedDataLabelOptions = [ + 'showVal' => false, + 'showCatName' => false, + 'showLegendKey' => true, + 'showSerName' => true, + 'showPercent' => true, + 'showLeaderLines' => true, + 'showBubbleSize' => true, + ]; + + $chart->setDataLabelOptions( + [ + 'showVal' => false, + 'showCatName' => false, + 'showLegendKey' => true, + 'showSerName' => true, + 'showPercent' => true, + 'showLeaderLines' => true, + 'showBubbleSize' => true, + ] + ); + self::assertEquals($chart->getDataLabelOptions(), $changedDataLabelOptions); + } + + /** + * Testing categoryLabelPosition getter and setter. + */ + public function testSetGetCategoryLabelPosition(): void + { + $chart = new Chart(); + + self::assertEquals($chart->getCategoryLabelPosition(), 'nextTo'); + + $chart->setCategoryLabelPosition('high'); + + self::assertEquals($chart->getCategoryLabelPosition(), 'high'); + } + + /** + * Testing valueLabelPosition getter and setter. + */ + public function testSetGetValueLabelPosition(): void + { + $chart = new Chart(); + + self::assertEquals($chart->getValueLabelPosition(), 'nextTo'); + + $chart->setValueLabelPosition('low'); + + self::assertEquals($chart->getValueLabelPosition(), 'low'); + } + + /** + * Testing categoryAxisTitle getter and setter. + */ + public function testSetGetCategoryAxisTitle(): void + { + $chart = new Chart(); + + self::assertEquals($chart->getCategoryAxisTitle(), null); + + $chart->setCategoryAxisTitle('Test Category Axis Title'); + + self::assertEquals($chart->getCategoryAxisTitle(), 'Test Category Axis Title'); + } + + /** + * Testing valueAxisTitle getter and setter. + */ + public function testSetGetValueAxisTitle(): void + { + $chart = new Chart(); + + self::assertEquals($chart->getValueAxisTitle(), null); + + $chart->setValueAxisTitle('Test Value Axis Title'); + + self::assertEquals($chart->getValueAxisTitle(), 'Test Value Axis Title'); + } +} diff --git a/tests/PhpWordTests/Style/FontTest.php b/tests/PhpWordTests/Style/FontTest.php new file mode 100644 index 0000000000..4ba6a762f3 --- /dev/null +++ b/tests/PhpWordTests/Style/FontTest.php @@ -0,0 +1,228 @@ + Jc::BOTH]); + + self::assertEquals('text', $object->getStyleType()); + self::assertInstanceOf(\PhpOffice\PhpWord\Style\Paragraph::class, $object->getParagraph()); + self::assertIsArray($object->getStyleValues()); + } + + /** + * Test setting style values with null or empty value. + */ + public function testSetStyleValueWithNullOrEmpty(): void + { + $object = new Font(); + + $attributes = [ + 'name' => null, + 'size' => null, + 'hint' => null, + 'color' => null, + 'bold' => false, + 'italic' => false, + 'underline' => Font::UNDERLINE_NONE, + 'superScript' => false, + 'subScript' => false, + 'strikethrough' => false, + 'doubleStrikethrough' => false, + 'smallCaps' => false, + 'allCaps' => false, + 'rtl' => false, + 'fgColor' => null, + 'bgColor' => null, + 'scale' => null, + 'spacing' => null, + 'kerning' => null, + 'lang' => null, + 'hidden' => false, + 'whiteSpace' => '', + 'fallbackFont' => '', + ]; + foreach ($attributes as $key => $default) { + $get = is_bool($default) ? "is{$key}" : "get{$key}"; + self::assertEquals($default, $object->$get()); + $object->setStyleValue($key, null); + self::assertEquals($default, $object->$get()); + $object->setStyleValue($key, ''); + self::assertEquals($default, $object->$get()); + } + } + + /** + * Test setting style values with normal value. + */ + public function testSetStyleValueNormal(): void + { + $object = new Font(); + + $attributes = [ + 'name' => 'Times New Roman', + 'size' => 9, + 'color' => '999999', + 'hint' => 'eastAsia', + 'bold' => true, + 'italic' => true, + 'underline' => Font::UNDERLINE_HEAVY, + 'superScript' => true, + 'subScript' => false, + 'strikethrough' => true, + 'doubleStrikethrough' => false, + 'smallCaps' => true, + 'allCaps' => false, + 'fgColor' => Font::FGCOLOR_YELLOW, + 'bgColor' => 'FFFF00', + 'lineHeight' => 2, + 'scale' => 150, + 'spacing' => 240, + 'kerning' => 10, + 'rtl' => true, + 'noProof' => true, + 'lang' => new Language(Language::EN_US), + 'hidden' => true, + 'whiteSpace' => 'pre-wrap', + 'fallbackFont' => 'serif', + ]; + $object->setStyleByArray($attributes); + foreach ($attributes as $key => $value) { + $get = is_bool($value) ? "is{$key}" : "get{$key}"; + self::assertEquals($value, $object->$get()); + } + } + + /** + * Test set line height. + */ + public function testLineHeight(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + // Test style array + $text = $section->addText('This is a test', ['line-height' => 2.0]); + + $doc = TestHelperDOCX::getDocument($phpWord); + $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:spacing'); + + $lineHeight = $element->getAttribute('w:line'); + $lineRule = $element->getAttribute('w:lineRule'); + + self::assertEquals(480, $lineHeight); + 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'); + + $lineHeight = $element->getAttribute('w:line'); + $lineRule = $element->getAttribute('w:lineRule'); + + self::assertEquals(720, $lineHeight); + self::assertEquals('auto', $lineRule); + } + + /** + * Test line height floatval. + */ + public function testLineHeightFloatval(): void + { + $object = new Font(null, ['alignment' => Jc::CENTER]); + $object->setLineHeight('1.5pt'); + self::assertEquals(1.5, $object->getLineHeight()); + } + + /** + * Test line height exception by using nonnumeric value. + */ + public function testLineHeightException(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\InvalidStyleException::class); + $object = new Font(); + $object->setLineHeight('a'); + } + + /** + * Test setting the language as a string. + */ + public function testSetLangAsString(): void + { + $object = new Font(); + $object->setLang(Language::FR_BE); + 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/ImageTest.php b/tests/PhpWordTests/Style/ImageTest.php new file mode 100644 index 0000000000..759a85441e --- /dev/null +++ b/tests/PhpWordTests/Style/ImageTest.php @@ -0,0 +1,108 @@ + 200, + 'height' => 200, + 'marginTop' => 240, + 'marginLeft' => 240, + 'wrapDistanceLeft' => 10, + 'wrapDistanceRight' => 20, + 'wrapDistanceTop' => 30, + 'wrapDistanceBottom' => 40, + ] as $key => $value) { + $set = "set{$key}"; + $get = "get{$key}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + } + + public function testSetGetNormalString(): void + { + $object = new Image(); + foreach ([ + 'alignment' => Jc::START, + 'wrappingStyle' => 'inline', + ] as $key => $value) { + $set = "set{$key}"; + $get = "get{$key}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + } + + /** + * Test setStyleValue method. + */ + public function testSetStyleValue(): void + { + $object = new Image(); + + $properties = [ + 'width' => 200, + 'height' => 200, + 'alignment' => Jc::START, + 'marginTop' => 240, + 'marginLeft' => 240, + 'position' => 10, + 'positioning' => Image::POSITION_ABSOLUTE, + 'posHorizontal' => Image::POSITION_HORIZONTAL_CENTER, + 'posVertical' => Image::POSITION_VERTICAL_TOP, + 'posHorizontalRel' => Image::POSITION_RELATIVE_TO_COLUMN, + 'posVerticalRel' => Image::POSITION_RELATIVE_TO_IMARGIN, + 'wrapDistanceLeft' => 10, + 'wrapDistanceRight' => 20, + 'wrapDistanceTop' => 30, + 'wrapDistanceBottom' => 40, + ]; + foreach ($properties as $key => $value) { + $get = "get{$key}"; + $object->setStyleValue("{$key}", $value); + self::assertEquals($value, $object->$get()); + } + } + + /** + * Test setWrappingStyle exception. + */ + public function testSetWrappingStyleException(): void + { + $this->expectException(InvalidArgumentException::class); + $object = new Image(); + $object->setWrappingStyle('foo'); + } +} diff --git a/tests/PhpWord/Tests/Style/IndentationTest.php b/tests/PhpWordTests/Style/IndentationTest.php similarity index 56% rename from tests/PhpWord/Tests/Style/IndentationTest.php rename to tests/PhpWordTests/Style/IndentationTest.php index d0e88f2cb2..76e3c74cb7 100644 --- a/tests/PhpWord/Tests/Style/IndentationTest.php +++ b/tests/PhpWordTests/Style/IndentationTest.php @@ -1,4 +1,5 @@ array(0, 10), - 'right' => array(0, 10), - 'firstLine' => array(null, 20), - 'hanging' => array(null, 20), - ); + $properties = [ + 'left' => [0, 10], + 'right' => [0, 10], + 'firstLine' => [null, 20], + 'firstLineChars' => [0, 20], + 'hanging' => [null, 20], + ]; foreach ($properties as $property => $value) { - list($default, $expected) = $value; + [$default, $expected] = $value; $get = "get{$property}"; $set = "set{$property}"; - $this->assertEquals($default, $object->$get()); // Default value + self::assertEquals($default, $object->$get()); // Default value $object->$set($expected); - $this->assertEquals($expected, $object->$get()); // New value + self::assertEquals($expected, $object->$get()); // New value } } } diff --git a/tests/PhpWordTests/Style/LanguageTest.php b/tests/PhpWordTests/Style/LanguageTest.php new file mode 100644 index 0000000000..848284e5e3 --- /dev/null +++ b/tests/PhpWordTests/Style/LanguageTest.php @@ -0,0 +1,92 @@ + [null, 1036], + ] as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } + + public function testGetSetPropertiesString(): void + { + $object = new Language(); + foreach ([ + 'latin' => [null, 'fr-BE'], + 'eastAsia' => [null, 'ja-JP'], + 'bidirectional' => [null, 'ar-SA'], + ] as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } + + /** + * Test throws exception if wrong locale is given. + */ + public function testWrongLanguage(): void + { + $this->expectException(InvalidArgumentException::class); + $language = new Language(); + $language->setLatin('fra'); + } + + /** + * Tests that a language can be set with just a 2 char code. + */ + public function testShortLanguage(): void + { + //when + $language = new Language(); + $language->setLatin('fr'); + + //then + Assert::assertEquals('fr-FR', $language->getLatin()); + } +} diff --git a/tests/PhpWordTests/Style/LineNumberingTest.php b/tests/PhpWordTests/Style/LineNumberingTest.php new file mode 100644 index 0000000000..c76bddf56c --- /dev/null +++ b/tests/PhpWordTests/Style/LineNumberingTest.php @@ -0,0 +1,67 @@ + [1, 2], + 'increment' => [1, 10], + 'distance' => [null, 10], + ] as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } + + public function testGetSetPropertiesString(): void + { + $object = new LineNumbering(); + foreach ([ + 'restart' => [null, 'continuous'], + ] as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } +} diff --git a/tests/PhpWordTests/Style/LineTest.php b/tests/PhpWordTests/Style/LineTest.php new file mode 100644 index 0000000000..26049d5cd8 --- /dev/null +++ b/tests/PhpWordTests/Style/LineTest.php @@ -0,0 +1,162 @@ + 10, + ] as $key => $value) { + $set = "set{$key}"; + $get = "get{$key}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + } + + public function testSetGetNormalString(): void + { + $object = new Line(); + + foreach ([ + 'connectorType' => Line::CONNECTOR_TYPE_STRAIGHT, + 'beginArrow' => Line::ARROW_STYLE_BLOCK, + 'endArrow' => Line::ARROW_STYLE_OVAL, + 'dash' => Line::DASH_STYLE_LONG_DASH_DOT_DOT, + 'color' => 'red', + ] as $key => $value) { + $set = "set{$key}"; + $get = "get{$key}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + } + + /** + * Test setStyleValue method. + */ + public function testSetStyleValue(): void + { + $object = new Line(); + + $properties = [ + 'connectorType' => Line::CONNECTOR_TYPE_STRAIGHT, + 'beginArrow' => Line::ARROW_STYLE_BLOCK, + 'endArrow' => Line::ARROW_STYLE_OVAL, + 'dash' => Line::DASH_STYLE_LONG_DASH_DOT_DOT, + 'weight' => 10, + 'color' => 'red', + ]; + foreach ($properties as $key => $value) { + $get = "get{$key}"; + $object->setStyleValue("{$key}", $value); + self::assertEquals($value, $object->$get()); + } + } + + /** + * Test set/get flip. + */ + public function testSetGetFlip(): void + { + $expected = true; + $object = new Line(); + $object->setFlip($expected); + self::assertEquals($expected, $object->isFlip()); + } + + /** + * Test set/get connectorType. + */ + public function testSetGetConnectorType(): void + { + $expected = Line::CONNECTOR_TYPE_STRAIGHT; + $object = new Line(); + $object->setConnectorType($expected); + self::assertEquals($expected, $object->getConnectorType()); + } + + /** + * Test set/get weight. + */ + public function testSetGetWeight(): void + { + $expected = 10; + $object = new Line(); + $object->setWeight($expected); + self::assertEquals($expected, $object->getWeight()); + } + + /** + * Test set/get color. + */ + public function testSetGetColor(): void + { + $expected = 'red'; + $object = new Line(); + $object->setColor($expected); + self::assertEquals($expected, $object->getColor()); + } + + /** + * Test set/get dash. + */ + public function testSetGetDash(): void + { + $expected = Line::DASH_STYLE_LONG_DASH_DOT_DOT; + $object = new Line(); + $object->setDash($expected); + self::assertEquals($expected, $object->getDash()); + } + + /** + * Test set/get beginArrow. + */ + public function testSetGetBeginArrow(): void + { + $expected = Line::ARROW_STYLE_BLOCK; + $object = new Line(); + $object->setBeginArrow($expected); + self::assertEquals($expected, $object->getBeginArrow()); + } + + /** + * Test set/get endArrow. + */ + public function testSetGetEndArrow(): void + { + $expected = Line::ARROW_STYLE_CLASSIC; + $object = new Line(); + $object->setEndArrow($expected); + self::assertEquals($expected, $object->getEndArrow()); + } +} diff --git a/tests/PhpWord/Tests/Style/ListItemTest.php b/tests/PhpWordTests/Style/ListItemTest.php similarity index 62% rename from tests/PhpWord/Tests/Style/ListItemTest.php rename to tests/PhpWordTests/Style/ListItemTest.php index a97c8dd604..59cdfeeda1 100644 --- a/tests/PhpWord/Tests/Style/ListItemTest.php +++ b/tests/PhpWordTests/Style/ListItemTest.php @@ -1,4 +1,5 @@ assertEquals($value, $object->getListType()); + self::assertEquals($value, $object->getListType()); } /** - * Test set style value + * Test set style value. */ - public function testSetStyleValue() + public function testSetStyleValue(): void { $object = new ListItem(); $value = ListItem::TYPE_ALPHANUM; $object->setStyleValue('listType', $value); - $this->assertEquals($value, $object->getListType()); + self::assertEquals($value, $object->getListType()); } /** - * Test list type + * Test list type. */ - public function testListType() + public function testListType(): void { $object = new ListItem(); $value = ListItem::TYPE_ALPHANUM; $object->setListType($value); - $this->assertEquals($value, $object->getListType()); + self::assertEquals($value, $object->getListType()); } /** - * Test set/get numbering style name + * Test set/get numbering style name. */ - public function testSetGetNumStyle() + public function testSetGetNumStyle(): void { $expected = 'List Name'; $object = new ListItem(); $object->setNumStyle($expected); - $this->assertEquals($expected, $object->getNumStyle()); + self::assertEquals($expected, $object->getNumStyle()); } } diff --git a/tests/PhpWord/Tests/Style/NumberingLevelTest.php b/tests/PhpWordTests/Style/NumberingLevelTest.php similarity index 61% rename from tests/PhpWord/Tests/Style/NumberingLevelTest.php rename to tests/PhpWordTests/Style/NumberingLevelTest.php index 8959a98317..b34e445e94 100644 --- a/tests/PhpWord/Tests/Style/NumberingLevelTest.php +++ b/tests/PhpWordTests/Style/NumberingLevelTest.php @@ -1,4 +1,5 @@ 1, 'start' => 1, - 'format' => 'decimal', 'restart' => 1, - 'pStyle' => 'pStyle', - 'suffix' => 'space', - 'text' => '%1.', - 'align' => 'left', 'left' => 360, 'hanging' => 360, 'tabPos' => 360, + ]; + foreach ($attributes as $key => $value) { + $set = "set{$key}"; + $get = "get{$key}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + } + + public function testSetGetNormalString(): void + { + $object = new NumberingLevel(); + + $attributes = [ + 'format' => 'decimal', + 'pStyle' => 'pStyle', + 'suffix' => 'space', + 'text' => '%1.', + 'alignment' => Jc::START, 'font' => 'Arial', 'hint' => 'default', - ); + ]; foreach ($attributes as $key => $value) { $set = "set{$key}"; $get = "get{$key}"; $object->$set($value); - $this->assertEquals($value, $object->$get()); + self::assertEquals($value, $object->$get()); } } } diff --git a/tests/PhpWordTests/Style/NumberingTest.php b/tests/PhpWordTests/Style/NumberingTest.php new file mode 100644 index 0000000000..153e4b9284 --- /dev/null +++ b/tests/PhpWordTests/Style/NumberingTest.php @@ -0,0 +1,72 @@ + [null, 1], + ] as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } + + public function testGetSetPropertiesString(): void + { + $object = new Numbering(); + foreach ([ + 'type' => [null, 'singleLevel'], + ] as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } + + public function testGetLevels(): void + { + $object = new Numbering(); + + self::assertEmpty($object->getLevels()); + } +} diff --git a/tests/PhpWordTests/Style/PaperTest.php b/tests/PhpWordTests/Style/PaperTest.php new file mode 100644 index 0000000000..ea5a4fb674 --- /dev/null +++ b/tests/PhpWordTests/Style/PaperTest.php @@ -0,0 +1,73 @@ +getSize()); + } + + /** + * Test paper size for B5 format. + */ + public function testB5Size(): void + { + $object = new Paper('B5'); + + self::assertEquals('B5', $object->getSize()); + self::assertEqualsWithDelta(9977.9527559055, $object->getWidth(), 0.000000001); + self::assertEqualsWithDelta(14173.228346457, $object->getHeight(), 0.000000001); + } + + /** + * Test paper size for Folio format. + */ + public function testFolioSize(): void + { + $object = new Paper(); + $object->setSize('Folio'); + + self::assertEquals('Folio', $object->getSize()); + self::assertEqualsWithDelta(12240, $object->getWidth(), 0.1); + self::assertEqualsWithDelta(18720, $object->getHeight(), 0.1); + } +} diff --git a/tests/PhpWordTests/Style/ParagraphTest.php b/tests/PhpWordTests/Style/ParagraphTest.php new file mode 100644 index 0000000000..67645fa4d8 --- /dev/null +++ b/tests/PhpWordTests/Style/ParagraphTest.php @@ -0,0 +1,385 @@ + true, + 'keepNext' => false, + 'keepLines' => false, + 'pageBreakBefore' => false, + 'contextualSpacing' => false, + ]; + foreach ($attributes as $key => $default) { + $get = $this->findGetter($key, $default, $object); + $object->setStyleValue($key, null); + self::assertEquals($default, $object->$get()); + $object->setStyleValue($key, ''); + self::assertEquals($default, $object->$get()); + } + } + + /** + * Test setting style values with normal value. + */ + public function testSetStyleValueNormal(): void + { + $object = new Paragraph(); + + $attributes = [ + 'spaceAfter' => 240, + 'spaceBefore' => 240, + 'indent' => 1, + 'hanging' => 1, + 'spacing' => 120, + 'spacingLineRule' => LineSpacingRule::AT_LEAST, + 'basedOn' => 'Normal', + 'next' => 'Normal', + 'numStyle' => 'numStyle', + 'numLevel' => 1, + 'widowControl' => false, + 'keepNext' => true, + 'keepLines' => true, + 'pageBreakBefore' => true, + 'contextualSpacing' => true, + 'textAlignment' => 'auto', + 'bidi' => true, + 'suppressAutoHyphens' => true, + ]; + foreach ($attributes as $key => $value) { + $get = $this->findGetter($key, $value, $object); + $object->setStyleValue("$key", $value); + 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)) { + if (method_exists($object, "is{$key}")) { + return "is{$key}"; + } elseif (method_exists($object, "has{$key}")) { + return "has{$key}"; + } + } + + return "get{$key}"; + } + + /** + * Test get null style value. + */ + public function testGetNullStyleValue(): void + { + $object = new Paragraph(); + + $attributes = ['spacing', 'indent', 'hanging', 'spaceBefore', 'spaceAfter', 'textAlignment']; + foreach ($attributes as $key) { + $get = $this->findGetter($key, null, $object); + self::assertNull($object->$get()); + } + } + + /** + * Test tabs. + */ + public function testTabs(): void + { + $object = new Paragraph(); + $object->setTabs([new Tab('left', 1550), new Tab('right', 5300)]); + self::assertCount(2, $object->getTabs()); + } + + public function testHanging(): void + { + $rand = mt_rand(0, 255); + + $object = new Paragraph(); + self::assertNull($object->getHanging()); + + $object->setHanging($rand); + self::assertEquals($rand, $object->getHanging()); + + $object->setHanging(null); + self::assertNull($object->getHanging()); + + $object->setHanging($rand); + self::assertEquals($rand, $object->getHanging()); + + $object->setHanging(); + self::assertNull($object->getHanging()); + } + + public function testIndent(): void + { + $rand = mt_rand(0, 255); + + $object = new Paragraph(); + self::assertNull($object->getIndent()); + + $object->setIndent($rand); + self::assertEquals($rand, $object->getIndent()); + + $object->setIndent(null); + self::assertNull($object->getIndent()); + + $object->setIndent($rand); + self::assertEquals($rand, $object->getIndent()); + + $object->setIndent(); + self::assertNull($object->getIndent()); + } + + public function testIndentation(): void + { + $rand = mt_rand(0, 255); + $rand2 = mt_rand(0, 255); + + $object = new Paragraph(); + self::assertNull($object->getIndentation()); + // Set Basic indentation + $object->setIndentation([]); + self::assertNotNull($object->getIndentation()); + self::assertEquals(0, $object->getIndentation()->getLeft()); + self::assertEquals(0, $object->getIndentation()->getRight()); + self::assertEquals(0, $object->getIndentation()->getHanging()); + self::assertEquals(0, $object->getIndentation()->getFirstLine()); + // Set indentation : left + $object->setIndentation([ + 'left' => $rand, + ]); + self::assertNotNull($object->getIndentation()); + self::assertEquals($rand, $object->getIndentation()->getLeft()); + self::assertEquals(0, $object->getIndentation()->getRight()); + self::assertEquals(0, $object->getIndentation()->getHanging()); + self::assertEquals(0, $object->getIndentation()->getFirstLine()); + // Set indentation : right + $object->setIndentation([ + 'right' => $rand, + ]); + self::assertNotNull($object->getIndentation()); + self::assertEquals($rand, $object->getIndentation()->getLeft()); + self::assertEquals($rand, $object->getIndentation()->getRight()); + self::assertEquals(0, $object->getIndentation()->getHanging()); + self::assertEquals(0, $object->getIndentation()->getFirstLine()); + // Set indentation : hanging + $object->setIndentation([ + 'hanging' => $rand, + ]); + self::assertNotNull($object->getIndentation()); + self::assertEquals($rand, $object->getIndentation()->getLeft()); + self::assertEquals($rand, $object->getIndentation()->getRight()); + self::assertEquals($rand, $object->getIndentation()->getHanging()); + self::assertEquals(0, $object->getIndentation()->getFirstLine()); + // Set indentation : firstline + $object->setIndentation([ + 'firstline' => $rand, + ]); + self::assertNotNull($object->getIndentation()); + self::assertEquals($rand, $object->getIndentation()->getLeft()); + self::assertEquals($rand, $object->getIndentation()->getRight()); + self::assertEquals($rand, $object->getIndentation()->getHanging()); + self::assertEquals($rand, $object->getIndentation()->getFirstLine()); + // Replace indentation : left & firstline + $object->setIndentation([ + 'left' => $rand2, + 'firstline' => $rand2, + ]); + self::assertNotNull($object->getIndentation()); + self::assertEquals($rand2, $object->getIndentation()->getLeft()); + self::assertEquals($rand, $object->getIndentation()->getRight()); + self::assertEquals($rand, $object->getIndentation()->getHanging()); + self::assertEquals($rand2, $object->getIndentation()->getFirstLine()); + // Replace indentation : N/A + $object->setIndentation(); + self::assertNotNull($object->getIndentation()); + self::assertEquals($rand2, $object->getIndentation()->getLeft()); + self::assertEquals($rand, $object->getIndentation()->getRight()); + self::assertEquals($rand, $object->getIndentation()->getHanging()); + self::assertEquals($rand2, $object->getIndentation()->getFirstLine()); + } + + public function testIndentFirstLine(): void + { + $rand = mt_rand(0, 255); + + $object = new Paragraph(); + self::assertNull($object->getIndentFirstLine()); + $object->setIndentFirstLine($rand); + self::assertEquals($rand, $object->getIndentFirstLine()); + $object->setIndentFirstLine(null); + self::assertNull($object->getIndentFirstLine()); + $object->setIndentFirstLine($rand); + self::assertEquals($rand, $object->getIndentFirstLine()); + $object->setIndentFirstLine(); + self::assertNull($object->getIndentFirstLine()); + } + + public function testIndentLeft(): void + { + $rand = mt_rand(0, 255); + + $object = new Paragraph(); + self::assertNull($object->getIndentLeft()); + $object->setIndentLeft($rand); + self::assertEquals($rand, $object->getIndentLeft()); + $object->setIndentLeft(null); + self::assertNull($object->getIndentLeft()); + $object->setIndentLeft($rand); + self::assertEquals($rand, $object->getIndentLeft()); + $object->setIndentLeft(); + self::assertNull($object->getIndentLeft()); + } + + public function testIndentRight(): void + { + $rand = mt_rand(0, 255); + + $object = new Paragraph(); + self::assertNull($object->getIndentRight()); + $object->setIndentRight($rand); + self::assertEquals($rand, $object->getIndentRight()); + $object->setIndentRight(null); + self::assertNull($object->getIndentRight()); + $object->setIndentRight($rand); + self::assertEquals($rand, $object->getIndentRight()); + $object->setIndentRight(); + self::assertNull($object->getIndentRight()); + } + + /** + * Line height. + */ + public function testLineHeight(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + // Test style array + $text = $section->addText('This is a test', [], ['line-height' => 2.0]); + + $doc = TestHelperDOCX::getDocument($phpWord); + $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:spacing'); + + $lineHeight = $element->getAttribute('w:line'); + $lineRule = $element->getAttribute('w:lineRule'); + + self::assertEquals(480, $lineHeight); + self::assertEquals('auto', $lineRule); + + // 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'); + + $lineHeight = $element->getAttribute('w:line'); + $lineRule = $element->getAttribute('w:lineRule'); + + self::assertEquals(720, $lineHeight); + self::assertEquals('auto', $lineRule); + } + + /** + * Test setLineHeight validation. + */ + public function testLineHeightValidation(): void + { + $object = new Paragraph(); + $object->setLineHeight('12.5pt'); + self::assertEquals(12.5, $object->getLineHeight()); + } + + /** + * Test line height exception by using nonnumeric value. + */ + public function testLineHeightException(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\InvalidStyleException::class); + $object = new Paragraph(); + $object->setLineHeight('a'); + } + + public function testBidiVisual(): void + { + $object = new Paragraph(); + self::assertNull($object->isBidi()); + $object->setBidi(true); + self::assertTrue($object->isBidi()); + $object->setBidi(false); + self::assertFalse($object->isBidi()); + $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/PhpWord/Tests/Style/RowTest.php b/tests/PhpWordTests/Style/RowTest.php similarity index 53% rename from tests/PhpWord/Tests/Style/RowTest.php rename to tests/PhpWordTests/Style/RowTest.php index 679e9982a3..d3cc480ac7 100644 --- a/tests/PhpWord/Tests/Style/RowTest.php +++ b/tests/PhpWordTests/Style/RowTest.php @@ -1,4 +1,5 @@ true, 'cantSplit' => false, 'exactHeight' => true, - ); + ]; foreach ($properties as $key => $value) { // set/get $set = "set{$key}"; - $get = "get{$key}"; + $get = "is{$key}"; $expected = $value ? 1 : 0; $object->$set($value); - $this->assertEquals($expected, $object->$get()); + self::assertEquals($expected, $object->$get()); // setStyleValue $value = !$value; $expected = $value ? 1 : 0; $object->setStyleValue("{$key}", $value); - $this->assertEquals($expected, $object->$get()); - } - } - - /** - * Test properties with nonboolean values, which will return default value - */ - public function testNonBooleanValue() - { - $object = new Row(); - - $properties = array( - 'tblHeader' => 'a', - 'cantSplit' => 'b', - 'exactHeight' => 'c', - ); - foreach ($properties as $key => $value) { - $set = "set{$key}"; - $get = "get{$key}"; - $object->$set($value); - $this->assertFalse($object->$get()); + self::assertEquals($expected, $object->$get()); } } } diff --git a/tests/PhpWordTests/Style/SectionTest.php b/tests/PhpWordTests/Style/SectionTest.php new file mode 100644 index 0000000000..e03dbb7596 --- /dev/null +++ b/tests/PhpWordTests/Style/SectionTest.php @@ -0,0 +1,350 @@ +getOrientation()); + self::assertEqualsWithDelta(Section::DEFAULT_WIDTH, $oSettings->getPageSizeW(), 0.000000001); + self::assertEqualsWithDelta(Section::DEFAULT_HEIGHT, $oSettings->getPageSizeH(), 0.000000001); + self::assertEquals('A4', $oSettings->getPaperSize()); + + $oSettings->setSettingValue('orientation', 'landscape'); + self::assertEquals('landscape', $oSettings->getOrientation()); + self::assertEqualsWithDelta(Section::DEFAULT_HEIGHT, $oSettings->getPageSizeW(), 0.000000001); + self::assertEqualsWithDelta(Section::DEFAULT_WIDTH, $oSettings->getPageSizeH(), 0.000000001); + + $iVal = mt_rand(1, 1000); + $oSettings->setSettingValue('borderSize', $iVal); + self::assertEquals([$iVal, $iVal, $iVal, $iVal], $oSettings->getBorderSize()); + self::assertEquals($iVal, $oSettings->getBorderBottomSize()); + self::assertEquals($iVal, $oSettings->getBorderLeftSize()); + self::assertEquals($iVal, $oSettings->getBorderRightSize()); + self::assertEquals($iVal, $oSettings->getBorderTopSize()); + + $oSettings->setSettingValue('borderColor', 'FF00AA'); + self::assertEquals(['FF00AA', 'FF00AA', 'FF00AA', 'FF00AA'], $oSettings->getBorderColor()); + self::assertEquals('FF00AA', $oSettings->getBorderBottomColor()); + self::assertEquals('FF00AA', $oSettings->getBorderLeftColor()); + self::assertEquals('FF00AA', $oSettings->getBorderRightColor()); + self::assertEquals('FF00AA', $oSettings->getBorderTopColor()); + + $iVal = mt_rand(1, 1000); + $oSettings->setSettingValue('headerHeight', $iVal); + self::assertEquals($iVal, $oSettings->getHeaderHeight()); + + $oSettings->setSettingValue('lineNumbering', []); + $oSettings->setSettingValue( + 'lineNumbering', + [ + 'start' => 1, + 'increment' => 1, + 'distance' => 240, + 'restart' => 'newPage', + ] + ); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\LineNumbering', $oSettings->getLineNumbering()); + + $oSettings->setSettingValue('lineNumbering', null); + self::assertNull($oSettings->getLineNumbering()); + } + + /** + * Set/get margin. + */ + public function testMargin(): void + { + // Section Settings + $oSettings = new Section(); + + $iVal = mt_rand(1, 1000); + $oSettings->setMarginTop($iVal); + self::assertEquals($iVal, $oSettings->getMarginTop()); + + $iVal = mt_rand(1, 1000); + $oSettings->setMarginBottom($iVal); + self::assertEquals($iVal, $oSettings->getMarginBottom()); + + $iVal = mt_rand(1, 1000); + $oSettings->setMarginLeft($iVal); + self::assertEquals($iVal, $oSettings->getMarginLeft()); + + $iVal = mt_rand(1, 1000); + $oSettings->setMarginRight($iVal); + self::assertEquals($iVal, $oSettings->getMarginRight()); + } + + /** + * Set/get page width. + */ + public function testPageWidth(): void + { + // Section Settings + $oSettings = new Section(); + + self::assertEqualsWithDelta(Section::DEFAULT_WIDTH, $oSettings->getPageSizeW(), 0.000000001); + $iVal = mt_rand(1, 1000); + $oSettings->setSettingValue('pageSizeW', $iVal); + self::assertEquals($iVal, $oSettings->getPageSizeW()); + } + + /** + * Set/get page height. + */ + public function testPageHeight(): void + { + // Section Settings + $oSettings = new Section(); + + self::assertEqualsWithDelta(Section::DEFAULT_HEIGHT, $oSettings->getPageSizeH(), 0.000000001); + $iVal = mt_rand(1, 1000); + $oSettings->setSettingValue('pageSizeH', $iVal); + self::assertEquals($iVal, $oSettings->getPageSizeH()); + } + + /** + * Set/get landscape orientation. + */ + public function testOrientationLandscape(): void + { + // Section Settings + $oSettings = new Section(); + + $oSettings->setLandscape(); + self::assertEquals('landscape', $oSettings->getOrientation()); + self::assertEqualsWithDelta(Section::DEFAULT_HEIGHT, $oSettings->getPageSizeW(), 0.000000001); + self::assertEqualsWithDelta(Section::DEFAULT_WIDTH, $oSettings->getPageSizeH(), 0.000000001); + } + + /** + * Set/get portrait orientation. + */ + public function testOrientationPortrait(): void + { + // Section Settings + $oSettings = new Section(); + + $oSettings->setPortrait(); + self::assertEquals('portrait', $oSettings->getOrientation()); + self::assertEqualsWithDelta(Section::DEFAULT_WIDTH, $oSettings->getPageSizeW(), 0.000000001); + self::assertEqualsWithDelta(Section::DEFAULT_HEIGHT, $oSettings->getPageSizeH(), 0.000000001); + } + + /** + * Set/get border size. + */ + public function testBorderSize(): void + { + // Section Settings + $oSettings = new Section(); + + $iVal = mt_rand(1, 1000); + $oSettings->setBorderSize($iVal); + self::assertEquals([$iVal, $iVal, $iVal, $iVal], $oSettings->getBorderSize()); + self::assertEquals($iVal, $oSettings->getBorderBottomSize()); + self::assertEquals($iVal, $oSettings->getBorderLeftSize()); + self::assertEquals($iVal, $oSettings->getBorderRightSize()); + self::assertEquals($iVal, $oSettings->getBorderTopSize()); + + $iVal = mt_rand(1, 1000); + $oSettings->setBorderBottomSize($iVal); + self::assertEquals($iVal, $oSettings->getBorderBottomSize()); + + $iVal = mt_rand(1, 1000); + $oSettings->setBorderLeftSize($iVal); + self::assertEquals($iVal, $oSettings->getBorderLeftSize()); + + $iVal = mt_rand(1, 1000); + $oSettings->setBorderRightSize($iVal); + self::assertEquals($iVal, $oSettings->getBorderRightSize()); + + $iVal = mt_rand(1, 1000); + $oSettings->setBorderTopSize($iVal); + self::assertEquals($iVal, $oSettings->getBorderTopSize()); + } + + /** + * Set/get border color. + */ + public function testBorderColor(): void + { + // Section Settings + $oSettings = new Section(); + + $oSettings->setBorderColor('FF00AA'); + self::assertEquals(['FF00AA', 'FF00AA', 'FF00AA', 'FF00AA'], $oSettings->getBorderColor()); + self::assertEquals('FF00AA', $oSettings->getBorderBottomColor()); + self::assertEquals('FF00AA', $oSettings->getBorderLeftColor()); + self::assertEquals('FF00AA', $oSettings->getBorderRightColor()); + self::assertEquals('FF00AA', $oSettings->getBorderTopColor()); + + $oSettings->setBorderBottomColor('BBCCDD'); + self::assertEquals('BBCCDD', $oSettings->getBorderBottomColor()); + + $oSettings->setBorderLeftColor('CCDDEE'); + self::assertEquals('CCDDEE', $oSettings->getBorderLeftColor()); + + $oSettings->setBorderRightColor('11EE22'); + self::assertEquals('11EE22', $oSettings->getBorderRightColor()); + + $oSettings->setBorderTopColor('22FF33'); + self::assertEquals('22FF33', $oSettings->getBorderTopColor()); + } + + /** + * Set/get page numbering start. + */ + public function testNumberingStart(): void + { + // Section Settings + $oSettings = new Section(); + + self::assertNull($oSettings->getPageNumberingStart()); + + $iVal = mt_rand(1, 1000); + $oSettings->setPageNumberingStart($iVal); + self::assertEquals($iVal, $oSettings->getPageNumberingStart()); + + $oSettings->setPageNumberingStart(); + self::assertNull($oSettings->getPageNumberingStart()); + } + + /** + * Set/get header height. + */ + public function testHeader(): void + { + $oSettings = new Section(); + + self::assertEquals(720, $oSettings->getHeaderHeight()); + + $iVal = mt_rand(1, 1000); + $oSettings->setHeaderHeight($iVal); + self::assertEquals($iVal, $oSettings->getHeaderHeight()); + + $oSettings->setHeaderHeight(); + self::assertEquals(720, $oSettings->getHeaderHeight()); + } + + /** + * Set/get footer height. + */ + public function testFooter(): void + { + // Section Settings + $oSettings = new Section(); + + self::assertEquals(720, $oSettings->getFooterHeight()); + + $iVal = mt_rand(1, 1000); + $oSettings->setFooterHeight($iVal); + self::assertEquals($iVal, $oSettings->getFooterHeight()); + + $oSettings->setFooterHeight(); + self::assertEquals(720, $oSettings->getFooterHeight()); + } + + /** + * Set/get column number. + */ + public function testColumnsNum(): void + { + // Section Settings + $oSettings = new Section(); + + // Default + self::assertEquals(1, $oSettings->getColsNum()); + + // Null value + $oSettings->setColsNum(); + self::assertEquals(1, $oSettings->getColsNum()); + + // Random value + $iVal = mt_rand(1, 1000); + $oSettings->setColsNum($iVal); + self::assertEquals($iVal, $oSettings->getColsNum()); + } + + /** + * Set/get column spacing. + */ + public function testColumnsSpace(): void + { + // Section Settings + $oSettings = new Section(); + + // Default + self::assertEquals(720, $oSettings->getColsSpace()); + + $iVal = mt_rand(1, 1000); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Section', $oSettings->setColsSpace($iVal)); + self::assertEquals($iVal, $oSettings->getColsSpace()); + + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Section', $oSettings->setColsSpace()); + self::assertEquals(720, $oSettings->getColsSpace()); + } + + /** + * Set/get break type. + */ + public function testBreakType(): void + { + // Section Settings + $oSettings = new Section(); + + self::assertNull($oSettings->getBreakType()); + + $oSettings->setBreakType('continuous'); + self::assertEquals('continuous', $oSettings->getBreakType()); + + $oSettings->setBreakType(); + self::assertNull($oSettings->getBreakType()); + } + + /** + * Vertical page alignment. + */ + public function testVerticalAlign(): void + { + // Section Settings + $oSettings = new Section(); + + self::assertNull($oSettings->getVAlign()); + + $oSettings->setVAlign(VerticalJc::BOTH); + self::assertEquals('both', $oSettings->getVAlign()); + } +} diff --git a/tests/PhpWord/Tests/Style/ShadingTest.php b/tests/PhpWordTests/Style/ShadingTest.php similarity index 57% rename from tests/PhpWord/Tests/Style/ShadingTest.php rename to tests/PhpWordTests/Style/ShadingTest.php index 5a965e1d10..a5fc7a89dd 100644 --- a/tests/PhpWord/Tests/Style/ShadingTest.php +++ b/tests/PhpWordTests/Style/ShadingTest.php @@ -1,4 +1,5 @@ array('clear', 'solid'), - 'color' => array(null, 'FF0000'), - 'fill' => array(null, 'FF0000'), - ); + $properties = [ + 'pattern' => ['clear', 'solid'], + 'color' => [null, 'FF0000'], + 'fill' => [null, 'FF0000'], + ]; foreach ($properties as $property => $value) { - list($default, $expected) = $value; + [$default, $expected] = $value; $get = "get{$property}"; $set = "set{$property}"; - $this->assertEquals($default, $object->$get()); // Default value + self::assertEquals($default, $object->$get()); // Default value $object->$set($expected); - $this->assertEquals($expected, $object->$get()); // New value + self::assertEquals($expected, $object->$get()); // New value } } } diff --git a/tests/PhpWordTests/Style/SpacingTest.php b/tests/PhpWordTests/Style/SpacingTest.php new file mode 100644 index 0000000000..351b9f1b54 --- /dev/null +++ b/tests/PhpWordTests/Style/SpacingTest.php @@ -0,0 +1,69 @@ + [null, 10], + 'after' => [null, 10], + 'line' => [null, 10], + ]; + foreach ($properties as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } + + public function testGetSetPropertiesString(): void + { + $object = new Spacing(); + $properties = [ + 'lineRule' => ['auto', 'exact'], + ]; + foreach ($properties as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } +} diff --git a/tests/PhpWordTests/Style/TOCTest.php b/tests/PhpWordTests/Style/TOCTest.php new file mode 100644 index 0000000000..039bc11340 --- /dev/null +++ b/tests/PhpWordTests/Style/TOCTest.php @@ -0,0 +1,66 @@ + [9062, 10], + 'indent' => [200, 10], + ] as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } + + public function testGetSetString(): void + { + $object = new TOC(); + foreach ([ + 'tabLeader' => [TOC::TAB_LEADER_DOT, TOC::TAB_LEADER_UNDERSCORE], + ] as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } +} diff --git a/tests/PhpWordTests/Style/TabTest.php b/tests/PhpWordTests/Style/TabTest.php new file mode 100644 index 0000000000..43b9ff2c85 --- /dev/null +++ b/tests/PhpWordTests/Style/TabTest.php @@ -0,0 +1,72 @@ + [0, 10], + ] as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } + + /** + * Test get/set. + */ + public function testGetSetPropertiesString(): void + { + $object = new Tab(); + foreach ([ + 'type' => [Tab::TAB_STOP_CLEAR, Tab::TAB_STOP_RIGHT], + 'leader' => [Tab::TAB_LEADER_NONE, Tab::TAB_LEADER_DOT], + ] as $property => $value) { + [$default, $expected] = $value; + $get = "get{$property}"; + $set = "set{$property}"; + + self::assertEquals($default, $object->$get()); // Default value + + $object->$set($expected); + + self::assertEquals($expected, $object->$get()); // New value + } + } +} diff --git a/tests/PhpWordTests/Style/TablePositionTest.php b/tests/PhpWordTests/Style/TablePositionTest.php new file mode 100644 index 0000000000..695bb3d297 --- /dev/null +++ b/tests/PhpWordTests/Style/TablePositionTest.php @@ -0,0 +1,83 @@ + TablePosition::VANCHOR_PAGE, 'bottomFromText' => 20]; + + $object = new TablePosition($styleTable); + self::assertEquals(TablePosition::VANCHOR_PAGE, $object->getVertAnchor()); + self::assertEquals(20, $object->getBottomFromText()); + } + + /** + * Test setting style with normal value. + */ + public function testSetGetNormalInt(): void + { + $object = new TablePosition(); + + foreach ([ + 'leftFromText' => 4, + 'rightFromText' => 4, + 'topFromText' => 4, + 'bottomFromText' => 4, + 'tblpX' => 5, + 'tblpY' => 6, + ] as $key => $value) { + $set = "set{$key}"; + $get = "get{$key}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + } + + /** + * Test setting style with normal value. + */ + public function testSetGetNormalString(): void + { + $object = new TablePosition(); + + foreach ([ + 'vertAnchor' => TablePosition::VANCHOR_PAGE, + 'horzAnchor' => TablePosition::HANCHOR_TEXT, + 'tblpXSpec' => TablePosition::XALIGN_CENTER, + 'tblpYSpec' => TablePosition::YALIGN_OUTSIDE, + ] as $key => $value) { + $set = "set{$key}"; + $get = "get{$key}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + } +} diff --git a/tests/PhpWordTests/Style/TableTest.php b/tests/PhpWordTests/Style/TableTest.php new file mode 100644 index 0000000000..e53a51f5df --- /dev/null +++ b/tests/PhpWordTests/Style/TableTest.php @@ -0,0 +1,255 @@ + 'FF0000']; + $styleFirstRow = ['borderBottomSize' => 3]; + + $object = new Table($styleTable, $styleFirstRow); + self::assertEquals('FF0000', $object->getBgColor()); + + $firstRow = $object->getFirstRow(); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Table', $firstRow); + self::assertEquals(3, $firstRow->getBorderBottomSize()); + } + + /** + * Test default values when passing no style. + */ + public function testDefaultValues(): void + { + $object = new Table(); + + self::assertNull($object->getBgColor()); + self::assertEquals(Table::LAYOUT_AUTO, $object->getLayout()); + self::assertEquals(TblWidth::AUTO, $object->getUnit()); + self::assertNull($object->getIndent()); + } + + /** + * Test setting style with normal value. + */ + public function testSetGetNormal(): void + { + $object = new Table(); + + $attributes = [ + 'bgColor' => 'FF0000', + 'borderTopSize' => 4, + 'borderTopColor' => 'FF0000', + 'borderLeftSize' => 4, + 'borderLeftColor' => 'FF0000', + 'borderRightSize' => 4, + 'borderRightColor' => 'FF0000', + 'borderBottomSize' => 4, + 'borderBottomColor' => 'FF0000', + 'borderInsideHSize' => 4, + 'borderInsideHColor' => 'FF0000', + 'borderInsideVSize' => 4, + 'borderInsideVColor' => 'FF0000', + 'cellMarginTop' => 240, + 'cellMarginLeft' => 240, + 'cellMarginRight' => 240, + 'cellMarginBottom' => 240, + 'alignment' => JcTable::CENTER, + 'width' => 100, + 'unit' => 'pct', + 'layout' => Table::LAYOUT_FIXED, + ]; + foreach ($attributes as $key => $value) { + $set = "set{$key}"; + $get = "get{$key}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + } + + 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. + * + * Set border color and test if each part has the same color + * While looping, push values array to be asserted with getBorderColor + */ + public function testBorderColor(): void + { + $object = new Table(); + $parts = ['Top', 'Left', 'Right', 'Bottom', 'InsideH', 'InsideV']; + + $value = 'FF0000'; + $object->setBorderColor($value); + $values = []; + foreach ($parts as $part) { + $get = "getBorder{$part}Color"; + $values[] = $value; + self::assertEquals($value, $object->$get()); + } + self::assertEquals($values, $object->getBorderColor()); + } + + /** + * Test border size. + * + * Set border size and test if each part has the same size + * While looping, push values array to be asserted with getBorderSize + * Value is in eights of a point, i.e. 4 / 8 = .5pt + */ + public function testBorderSize(): void + { + $object = new Table(); + $parts = ['Top', 'Left', 'Right', 'Bottom', 'InsideH', 'InsideV']; + + $value = 4; + $object->setBorderSize($value); + $values = []; + foreach ($parts as $part) { + $get = "getBorder{$part}Size"; + $values[] = $value; + self::assertEquals($value, $object->$get()); + } + self::assertEquals($values, $object->getBorderSize()); + } + + /** + * Test cell margin. + * + * Set cell margin and test if each part has the same margin + * While looping, push values array to be asserted with getCellMargin + * Value is in twips + */ + public function testCellMargin(): void + { + $object = new Table(); + $parts = ['Top', 'Left', 'Right', 'Bottom']; + + $value = 240; + $object->setCellMargin($value); + $values = []; + foreach ($parts as $part) { + $get = "getCellMargin{$part}"; + $values[] = $value; + self::assertEquals($value, $object->$get()); + } + self::assertEquals($values, $object->getCellMargin()); + self::assertTrue($object->hasMargin()); + } + + /** + * Set style value for various special value types. + */ + public function testSetStyleValue(): void + { + $object = new Table(); + $object->setStyleValue('borderSize', 120); + $object->setStyleValue('cellMargin', 240); + $object->setStyleValue('borderColor', '999999'); + + self::assertEquals([120, 120, 120, 120, 120, 120], $object->getBorderSize()); + self::assertEquals([240, 240, 240, 240], $object->getCellMargin()); + self::assertEquals( + ['999999', '999999', '999999', '999999', '999999', '999999'], + $object->getBorderColor() + ); + } + + /** + * Tests table cell spacing. + */ + public function testTableCellSpacing(): void + { + $object = new Table(); + self::assertNull($object->getCellSpacing()); + + $object = new Table(['cellSpacing' => 20]); + self::assertEquals(20, $object->getCellSpacing()); + } + + /** + * Tests table floating position. + */ + public function testTablePosition(): void + { + $object = new Table(); + self::assertNull($object->getPosition()); + + $object->setPosition(['vertAnchor' => TablePosition::VANCHOR_PAGE]); + self::assertNotNull($object->getPosition()); + self::assertEquals(TablePosition::VANCHOR_PAGE, $object->getPosition()->getVertAnchor()); + } + + public function testIndent(): void + { + $indent = new TblWidthComplexType(100, TblWidth::TWIP); + + $table = new Table(['indent' => $indent]); + + self::assertSame($indent, $table->getIndent()); + } +} diff --git a/tests/PhpWordTests/Style/TextBoxTest.php b/tests/PhpWordTests/Style/TextBoxTest.php new file mode 100644 index 0000000000..30c01bd368 --- /dev/null +++ b/tests/PhpWordTests/Style/TextBoxTest.php @@ -0,0 +1,321 @@ + 200, + 'height' => 200, + 'alignment' => Jc::START, + 'marginTop' => 240, + 'marginLeft' => 240, + 'wrappingStyle' => 'inline', + 'positioning' => 'absolute', + 'posHorizontal' => 'center', + 'posVertical' => 'top', + 'posHorizontalRel' => 'margin', + 'posVerticalRel' => 'page', + 'innerMarginTop' => '5', + 'innerMarginRight' => '5', + 'innerMarginBottom' => '5', + 'innerMarginLeft' => '5', + 'borderSize' => '2', + 'borderColor' => 'red', + 'bgColor' => 'blue', + ]; + foreach ($properties as $key => $value) { + $set = "set{$key}"; + $get = "get{$key}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + } + + /** + * Test setStyleValue method. + */ + public function testSetStyleValue(): void + { + $object = new TextBox(); + + $properties = [ + 'width' => 200, + 'height' => 200, + 'alignment' => Jc::START, + 'marginTop' => 240, + 'marginLeft' => 240, + 'wrappingStyle' => 'inline', + 'positioning' => 'absolute', + 'posHorizontal' => 'center', + 'posVertical' => 'top', + 'posHorizontalRel' => 'margin', + 'posVerticalRel' => 'page', + 'innerMarginTop' => '5', + 'innerMarginRight' => '5', + 'innerMarginBottom' => '5', + 'innerMarginLeft' => '5', + 'borderSize' => '2', + 'borderColor' => 'red', + 'bgColor' => 'blue', + ]; + foreach ($properties as $key => $value) { + $get = "get{$key}"; + $object->setStyleValue("{$key}", $value); + self::assertEquals($value, $object->$get()); + } + } + + /** + * Test setWrappingStyle exception. + */ + public function testSetWrappingStyleException(): void + { + $this->expectException(InvalidArgumentException::class); + $object = new TextBox(); + $object->setWrappingStyle('foo'); + } + + /** + * Test set/get width. + */ + public function testSetGetWidth(): void + { + $expected = 200; + $object = new TextBox(); + $object->setWidth($expected); + self::assertEquals($expected, $object->getWidth()); + } + + /** + * Test set/get height. + */ + public function testSetGetHeight(): void + { + $expected = 200; + $object = new TextBox(); + $object->setHeight($expected); + self::assertEquals($expected, $object->getHeight()); + } + + /** + * Test set/get height. + */ + public function testSetGetAlign(): void + { + $textBox = new TextBox(); + + $expectedAlignment = Jc::START; + $textBox->setAlignment($expectedAlignment); + self::assertEquals($expectedAlignment, $textBox->getAlignment()); + } + + /** + * Test set/get marginTop. + */ + public function testSetGetMarginTop(): void + { + $expected = 5; + $object = new TextBox(); + $object->setMarginTop($expected); + self::assertEquals($expected, $object->getMarginTop()); + } + + /** + * Test set/get marginLeft. + */ + public function testSetGetMarginLeft(): void + { + $expected = 5; + $object = new TextBox(); + $object->setMarginLeft($expected); + self::assertEquals($expected, $object->getMarginLeft()); + } + + /** + * Test set/get innerMarginTop. + */ + public function testSetGetInnerMarginTop(): void + { + $expected = 5; + $object = new TextBox(); + $object->setInnerMarginTop($expected); + self::assertEquals($expected, $object->getInnerMarginTop()); + } + + /** + * Test set/get wrappingStyle. + */ + public function testSetGetWrappingStyle(): void + { + $expected = 'inline'; + $object = new TextBox(); + $object->setWrappingStyle($expected); + self::assertEquals($expected, $object->getWrappingStyle()); + } + + /** + * Test set/get positioning. + */ + public function testSetGetPositioning(): void + { + $expected = 'absolute'; + $object = new TextBox(); + $object->setPositioning($expected); + self::assertEquals($expected, $object->getPositioning()); + } + + /** + * Test set/get posHorizontal. + */ + public function testSetGetPosHorizontal(): void + { + $expected = 'center'; + $object = new TextBox(); + $object->setPosHorizontal($expected); + self::assertEquals($expected, $object->getPosHorizontal()); + } + + /** + * Test set/get posVertical. + */ + public function testSetGetPosVertical(): void + { + $expected = 'top'; + $object = new TextBox(); + $object->setPosVertical($expected); + self::assertEquals($expected, $object->getPosVertical()); + } + + /** + * Test set/get posHorizontalRel. + */ + public function testSetGetPosHorizontalRel(): void + { + $expected = 'margin'; + $object = new TextBox(); + $object->setPosHorizontalRel($expected); + self::assertEquals($expected, $object->getPosHorizontalRel()); + } + + /** + * Test set/get posVerticalRel. + */ + public function testSetGetPosVerticalRel(): void + { + $expected = 'page'; + $object = new TextBox(); + $object->setPosVerticalRel($expected); + self::assertEquals($expected, $object->getPosVerticalRel()); + } + + /** + * Test set/get innerMarginRight. + */ + public function testSetGetInnerMarginRight(): void + { + $expected = 5; + $object = new TextBox(); + $object->setInnerMarginRight($expected); + self::assertEquals($expected, $object->getInnerMarginRight()); + } + + /** + * Test set/get innerMarginBottom. + */ + public function testSetGetInnerMarginBottom(): void + { + $expected = 5; + $object = new TextBox(); + $object->setInnerMarginBottom($expected); + self::assertEquals($expected, $object->getInnerMarginBottom()); + } + + /** + * Test set/get innerMarginLeft. + */ + public function testSetGetInnerMarginLeft(): void + { + $expected = 5; + $object = new TextBox(); + $object->setInnerMarginLeft($expected); + self::assertEquals($expected, $object->getInnerMarginLeft()); + } + + /** + * Test set/get innerMarginLeft. + */ + public function testSetGetInnerMargin(): void + { + $expected = 5; + $object = new TextBox(); + $object->setInnerMargin($expected); + self::assertEquals([$expected, $expected, $expected, $expected], $object->getInnerMargin()); + } + + /** + * Test set/get borderSize. + */ + public function testSetGetBorderSize(): void + { + $expected = 2; + $object = new TextBox(); + $object->setBorderSize($expected); + self::assertEquals($expected, $object->getBorderSize()); + } + + /** + * Test set/get borderColor. + */ + public function testSetGetBorderColor(): void + { + $expected = 'red'; + $object = new TextBox(); + $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/PhpWord/Tests/StyleTest.php b/tests/PhpWordTests/StyleTest.php similarity index 51% rename from tests/PhpWord/Tests/StyleTest.php rename to tests/PhpWordTests/StyleTest.php index 6165e4fd3b..6115e04426 100644 --- a/tests/PhpWord/Tests/StyleTest.php +++ b/tests/PhpWordTests/StyleTest.php @@ -1,4 +1,5 @@ 'center'); - $font = array('italic' => true, '_bold' => true); - $table = array('bgColor' => 'CCCCCC'); - $styles = array( + $paragraph = ['alignment' => Jc::CENTER]; + $font = ['italic' => true, '_bold' => true]; + $table = ['bgColor' => 'CCCCCC']; + $numbering = [ + 'type' => 'multilevel', + 'levels' => [ + [ + 'start' => 1, + 'format' => 'decimal', + 'restart' => 1, + 'suffix' => 'space', + 'text' => '%1.', + 'alignment' => Jc::START, + ], + ], + ]; + + $styles = [ 'Paragraph' => 'Paragraph', - 'Font' => 'Font', - 'Link' => 'Font', - 'Table' => 'Table', + 'Font' => 'Font', + 'Link' => 'Font', + 'Table' => 'Table', 'Heading_1' => 'Font', - 'Normal' => 'Paragraph', - ); + 'Normal' => 'Paragraph', + 'Numbering' => 'Numbering', + ]; Style::addParagraphStyle('Paragraph', $paragraph); Style::addFontStyle('Font', $font); Style::addLinkStyle('Link', $font); - // @todo Style::addNumberingStyle + Style::addNumberingStyle('Numbering', $numbering); Style::addTitleStyle(1, $font); Style::addTableStyle('Table', $table); Style::setDefaultParagraphStyle($paragraph); - $this->assertCount(count($styles), Style::getStyles()); + self::assertCount(count($styles), Style::getStyles()); foreach ($styles as $name => $style) { - $this->assertInstanceOf("PhpOffice\\PhpWord\\Style\\{$style}", Style::getStyle($name)); + self::assertInstanceOf("PhpOffice\\PhpWord\\Style\\{$style}", Style::getStyle($name)); } - $this->assertNull(Style::getStyle('Unknown')); + self::assertNull(Style::getStyle('Unknown')); Style::resetStyles(); - $this->assertCount(0, Style::getStyles()); + self::assertCount(0, Style::getStyles()); } /** - * Test default paragraph style + * Test default paragraph style. * * @covers ::setDefaultParagraphStyle - * @test */ - public function testDefaultParagraphStyle() + public function testDefaultParagraphStyle(): void { - $paragraph = array('align' => 'center'); + $paragraph = ['alignment' => Jc::CENTER]; Style::setDefaultParagraphStyle($paragraph); - $this->assertInstanceOf("PhpOffice\\PhpWord\\Style\\Paragraph", Style::getStyle('Normal')); + self::assertInstanceOf('PhpOffice\\PhpWord\\Style\\Paragraph', Style::getStyle('Normal')); } } diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php new file mode 100644 index 0000000000..8ae4dfa59a --- /dev/null +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -0,0 +1,1781 @@ +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 = $this->getTemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); + self::assertEquals([], $object->getVariables()); + $object->save(); + + try { + $object->zip()->close(); + self::fail('Expected exception for double close'); + } catch (Throwable $e) { + // nothing to do here + } + } + + /** + * Template can be saved in temporary location. + * + * @covers ::save + * @covers ::zip + */ + public function xtestTemplateCanBeSavedInTemporaryLocation(string $templateFqfn, TemplateProcessor $templateProcessor): string + { + $xslDomDocument = new DOMDocument(); + $xslDomDocument->load(__DIR__ . '/_files/xsl/remove_tables_by_needle.xsl'); + foreach (['${employee.', '${scoreboard.', '${reference.'] as $needle) { + $templateProcessor->applyXslStyleSheet($xslDomDocument, ['needle' => $needle]); + } + + $embeddingText = 'The quick Brown Fox jumped over the lazy^H^H^H^Htired unitTester'; + $templateProcessor->zip()->AddFromString('word/embeddings/fox.bin', $embeddingText); + $documentFqfn = $templateProcessor->save(); + + self::assertNotEmpty($documentFqfn, 'FQFN of the saved document is empty.'); + self::assertFileExists($documentFqfn, "The saved document \"{$documentFqfn}\" doesn't exist."); + + $templateZip = new ZipArchive(); + $templateZip->open($templateFqfn); + $templateHeaderXml = $templateZip->getFromName('word/header1.xml'); + $templateMainPartXml = $templateZip->getFromName('word/document.xml'); + $templateFooterXml = $templateZip->getFromName('word/footer1.xml'); + if (false === $templateZip->close()) { + throw new Exception("Could not close zip file \"{$templateZip}\"."); + } + + $documentZip = new ZipArchive(); + $documentZip->open($documentFqfn); + $documentHeaderXml = $documentZip->getFromName('word/header1.xml'); + $documentMainPartXml = $documentZip->getFromName('word/document.xml'); + $documentFooterXml = $documentZip->getFromName('word/footer1.xml'); + $documentEmbedding = $documentZip->getFromName('word/embeddings/fox.bin'); + if (false === $documentZip->close()) { + throw new Exception("Could not close zip file \"{$documentZip}\"."); + } + + self::assertNotEquals($templateHeaderXml, $documentHeaderXml); + self::assertNotEquals($templateMainPartXml, $documentMainPartXml); + self::assertNotEquals($templateFooterXml, $documentFooterXml); + self::assertEquals($embeddingText, $documentEmbedding); + + return $documentFqfn; + } + + /** + * XSL stylesheet can be applied. + * + * @covers ::applyXslStyleSheet + */ + 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(); + $actualDocumentZip->open($actualDocumentFqfn); + $actualHeaderXml = $actualDocumentZip->getFromName('word/header1.xml'); + $actualMainPartXml = $actualDocumentZip->getFromName('word/document.xml'); + $actualFooterXml = $actualDocumentZip->getFromName('word/footer1.xml'); + if (false === $actualDocumentZip->close()) { + throw new Exception("Could not close zip file \"{$actualDocumentFqfn}\"."); + } + + $expectedDocumentZip = new ZipArchive(); + $expectedDocumentZip->open($expectedDocumentFqfn); + $expectedHeaderXml = $expectedDocumentZip->getFromName('word/header1.xml'); + $expectedMainPartXml = $expectedDocumentZip->getFromName('word/document.xml'); + $expectedFooterXml = $expectedDocumentZip->getFromName('word/footer1.xml'); + if (false === $expectedDocumentZip->close()) { + throw new Exception("Could not close zip file \"{$expectedDocumentFqfn}\"."); + } + + self::assertSame($expectedHeaderXml, $actualHeaderXml); + self::assertSame($expectedMainPartXml, $actualMainPartXml); + self::assertSame($expectedFooterXml, $actualFooterXml); + } + + /** + * XSL stylesheet cannot be applied on failure in setting parameter value. + * + * @covers ::applyXslStyleSheet + */ + public function testXslStyleSheetCanNotBeAppliedOnFailureOfSettingParameterValue(): void + { + if (\PHP_VERSION_ID >= 80000) { + // 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 = $this->getTemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); + + $xslDomDocument = new DOMDocument(); + $xslDomDocument->load(__DIR__ . '/_files/xsl/passthrough.xsl'); + + /* + * We have to use error control below, because \XSLTProcessor::setParameter omits warning on failure. + * This warning fails the test. + */ + @$templateProcessor->applyXslStyleSheet($xslDomDocument, [1 => 'somevalue']); + } + + /** + * XSL stylesheet can be applied on failure of loading XML from template. + * + * @covers ::applyXslStyleSheet + */ + public function testXslStyleSheetCanNotBeAppliedOnFailureOfLoadingXmlFromTemplate(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectExceptionMessage('Could not load the given XML document.'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/corrupted_main_document_part.docx'); + + $xslDomDocument = new DOMDocument(); + $xslDomDocument->load(__DIR__ . '/_files/xsl/passthrough.xsl'); + + /* + * We have to use error control below, because \DOMDocument::loadXML omits warning on failure. + * This warning fails the test. + */ + @$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 + * @covers ::setValue + */ + public function testCloneRow(): void + { + $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'], + $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 testCloneRowAndSetValues(): void + { + $mainPart = ' + + + + + + + + ${userId} + + + + + + + ${userName} + + + + + + + + + + + + + + + ${userLocation} + + + + + '; + $templateProcessor = new TestableTemplateProcesor($mainPart); + + 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('Superman', $templateProcessor->getMainPart()); + self::assertStringContainsString('Metropolis', $templateProcessor->getMainPart()); + } + + public function testCloneNotExistingRowShouldThrowException(): void + { + $this->expectException(Exception::class); + $mainPart = 'text'; + $templateProcessor = new TestableTemplateProcesor($mainPart); + + $templateProcessor->cloneRow('fake_search', 2); + } + + /** + * @covers ::cloneRow + * @covers ::saveAs + * @covers ::setValue + */ + public function testCloneRowAndSetValuesWithCustomMacro(): void + { + $mainPart = ' + + + + + + + + {{userId}} + + + + + + + {{userName}} + + + + + + + + + + + + + + + {{userLocation}} + + + + + '; + $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('Superman', $templateProcessor->getMainPart()); + self::assertStringContainsString('Metropolis', $templateProcessor->getMainPart()); + } + + /** + * @covers ::saveAs + * @covers ::setValue + */ + public function testMacrosCanBeReplacedInHeaderAndFooter(): void + { + $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()); + + $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 ::setValue + */ + public function testSetValue(): void + { + $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); + self::assertEquals( + ['tableHeader', 'userId', 'userLocation'], + $templateProcessor->getVariables() + ); + } + + public function testSetComplexValue(): void + { + $title = new TextRun(); + $title->addText('This is my title'); + + $firstname = new Text('Donald'); + $lastname = new Text('Duck'); + + $mainPart = ' + + + Hello ${document-title} + + + + + Hello ${firstname} ${lastname} + + '; + + $result = ' + + + + + This is my title + + + + + Hello + + + + Donald + + + + + + + Duck + + '; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setComplexBlock('document-title', $title); + $templateProcessor->setComplexValue('firstname', $firstname); + $templateProcessor->setComplexValue('lastname', $lastname); + + 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 = ' + + + Hello {{document-title}} + + + + + Hello {{firstname}} {{lastname}} + + '; + + $result = ' + + + + + This is my title + + + + + Hello + + + + Donald + + + + + + + Duck + + '; + + $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 + */ + public function testSetValues(): void + { + $mainPart = ' + + + Hello ${firstname} ${lastname} + + + + + Hello ${firstname} ${lastname} + + + + + Hello ${firstname} ${lastname} + + + '; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setValues(['firstname' => 'John', 'lastname' => 'Doe']); + self::assertStringContainsString('Hello John Doe', $templateProcessor->getMainPart()); + self::assertStringNotContainsString('Hello ${firstname} ${lastname}', $templateProcessor->getMainPart()); + + // test with a specific limit that is lower than the number of replacements + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setValues(['firstname' => 'Jane', 'lastname' => 'Smith'], 2); + $variablesCounts = $templateProcessor->getVariableCount(); + + self::assertStringContainsString('Hello Jane Smith', $templateProcessor->getMainPart()); + self::assertStringContainsString('Hello ${firstname} ${lastname}', $templateProcessor->getMainPart()); + self::assertEquals(1, $variablesCounts['firstname']); + self::assertEquals(1, $variablesCounts['lastname']); + + // test with a limit for only one replacement + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setValues(['firstname' => 'Alice', 'lastname' => 'Wonderland'], 1); + $variablesCounts = $templateProcessor->getVariableCount(); + + self::assertStringContainsString('Hello Alice Wonderland', $templateProcessor->getMainPart()); + self::assertStringContainsString('Hello ${firstname} ${lastname}', $templateProcessor->getMainPart()); + self::assertEquals(2, $variablesCounts['firstname']); + self::assertEquals(2, $variablesCounts['lastname']); + + // Test with a limit of 0 for a result with no replacements + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setValues(['firstname' => 'Test', 'lastname' => 'User'], 0); + $variablesCounts = $templateProcessor->getVariableCount(); + + self::assertStringContainsString('Hello ${firstname} ${lastname}', $templateProcessor->getMainPart()); + self::assertStringNotContainsString('Hello Test User', $templateProcessor->getMainPart()); + self::assertEquals(3, $variablesCounts['firstname']); + self::assertEquals(3, $variablesCounts['lastname']); + } + + /** + * @covers ::setValues + */ + public function testSetValuesMultiLine(): void + { + $mainPart = ' + + + Address: ${address} + + '; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setValues(['address' => "Peter Pan\nNeverland"]); + + self::assertStringContainsString('Address: Peter PanNeverland', $templateProcessor->getMainPart()); + } + + /** + * @covers ::setValues + */ + public function testSetValuesWithCustomMacro(): void + { + $mainPart = ' + + + Hello {#firstname#} {#lastname#} + + '; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setMacroChars('{#', '#}'); + $templateProcessor->setValues(['firstname' => 'John', 'lastname' => 'Doe']); + + self::assertStringContainsString('Hello John Doe', $templateProcessor->getMainPart()); + } + + /** + * @covers ::setCheckbox + */ + public function testSetCheckbox(): void + { + $mainPart = ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '; + + $result = ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '; + + $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 = ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '; + + $result = ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '; + + $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'; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('${Test:width=100:ratio=true}'); + $objWriter = IOFactory::createWriter($phpWord, 'Word2007'); + $objWriter->save($testFileName); + self::assertFileExists($testFileName, "Generated file '{$testFileName}' not found!"); + + $resultFileName = 'images-test-result.docx'; + $templateProcessor = new TemplateProcessor($testFileName); + unlink($testFileName); + $templateProcessor->setImageValue('Test', $imagePath); + $templateProcessor->setImageValue('Test1', $imagePath); + $templateProcessor->setImageValue('Test2', $imagePath); + $templateProcessor->saveAs($resultFileName); + self::assertFileExists($resultFileName, "Generated file '{$resultFileName}' not found!"); + + $expectedDocumentZip = new ZipArchive(); + $expectedDocumentZip->open($resultFileName); + $expectedMainPartXml = $expectedDocumentZip->getFromName('word/document.xml'); + if (false === $expectedDocumentZip->close()) { + throw new Exception("Could not close zip file \"{$resultFileName}\"."); + } + unlink($resultFileName); + + self::assertStringNotContainsString('${Test}', $expectedMainPartXml, 'word/document.xml has no image.'); + } + + /** + * @covers ::cloneBlock + * @covers ::deleteBlock + * @covers ::saveAs + */ + public function testCloneDeleteBlock(): void + { + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/clone-delete-block.docx'); + + self::assertEquals( + ['DELETEME', '/DELETEME', 'CLONEME', 'blockVariable', '/CLONEME'], + $templateProcessor->getVariables() + ); + + $docName = 'clone-delete-block-result.docx'; + $templateProcessor->cloneBlock('CLONEME', 3); + $templateProcessor->deleteBlock('DELETEME'); + $templateProcessor->setValue('blockVariable#3', 'Test'); + $templateProcessor->saveAs($docName); + $docFound = file_exists($docName); + unlink($docName); + self::assertTrue($docFound); + } + + /** + * @covers ::getVariableCount + */ + public function testGetVariableCountCountsHowManyTimesEachPlaceholderIsPresent(): 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); + $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); + + 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 ::cloneBlock + */ + public function testCloneBlockCanCloneABlockTwice(): 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->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 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 + */ + 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 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 + */ + public function testCloneBlock(): void + { + $mainPart = ' + + + + ${CLONEME} + + + + + This block will be cloned with ${variable} + + + + + ${/CLONEME} + + '; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->cloneBlock('CLONEME', 3); + + self::assertEquals(3, substr_count($templateProcessor->getMainPart(), 'This block will be cloned with ${variable}')); + } + + /** + * @covers ::cloneBlock + */ + public function testCloneBlockWithCustomMacro(): void + { + $mainPart = ' + + + + {{CLONEME}} + + + + + This block will be cloned with {{variable}} + + + + + {{/CLONEME}} + + '; + + $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 + */ + public function testCloneBlockWithVariables(): void + { + $mainPart = ' + + + + ${CLONEME} + + + + + Address ${address}, Street ${street} + + + + + ${/CLONEME} + + '; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $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()); + } + + /** + * @covers ::cloneBlock + */ + public function testCloneBlockWithVariablesAndCustomMacro(): void + { + $mainPart = ' + + + + {{CLONEME}} + + + + + Address {{address}}, Street {{street}} + + + + + {{/CLONEME}} + + '; + + $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 = ' + + + + ${CLONEME} + + + + + City: ${city}, Street: ${street} + + + + + ${/CLONEME} + + '; + + $replacements = [ + ['city' => 'London', 'street' => 'Baker Street'], + ['city' => 'New York', 'street' => '5th Avenue'], + ['city' => 'Rome', 'street' => 'Via della Conciliazione'], + ]; + $templateProcessor = new TestableTemplateProcesor($mainPart); + $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()); + } + + public function testCloneBlockWithVariableReplacementsAndCustomMacro(): void + { + $mainPart = ' + + + + {{CLONEME}} + + + + + City: {{city}}, Street: {{street}} + + + + + {{/CLONEME}} + + '; + + $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. + * + * @covers ::fixBrokenMacros + */ + public function testFixBrokenMacros(): void + { + $templateProcessor = new TestableTemplateProcesor(); + + $fixed = $templateProcessor->fixBrokenMacros('normal text'); + self::assertEquals('normal text', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('${documentContent}'); + self::assertEquals('${documentContent}', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('${documentContent}'); + self::assertEquals('${documentContent}', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('$1500${documentContent}'); + self::assertEquals('$1500${documentContent}', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('$1500${documentContent}'); + self::assertEquals('$1500${documentContent}', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('25$ plus some info {hint}'); + self::assertEquals('25$ plus some info {hint}', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('$15,000.00. ${variable_name}'); + self::assertEquals('$15,000.00. ${variable_name}', $fixed); + } + + /** + * Template macros can be fixed even with cutome macro. + * + * @covers ::fixBrokenMacros + */ + public function testFixBrokenMacrosWithCustomMacro(): void + { + $templateProcessor = new TestableTemplateProcesor(); + $templateProcessor->setMacroChars('{{', '}}'); + + $fixed = $templateProcessor->fixBrokenMacros('normal text'); + self::assertEquals('normal text', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('{{documentContent}}'); + self::assertEquals('{{documentContent}}', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('{{documentContent}}'); + self::assertEquals('{{documentContent}}', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('$1500{{documentContent}}'); + self::assertEquals('$1500{{documentContent}}', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('$1500{{documentContent}}'); + self::assertEquals('$1500{{documentContent}}', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('25$ plus some info {hint}'); + self::assertEquals('25$ plus some info {hint}', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('$15,000.00. {{variable_name}}'); + self::assertEquals('$15,000.00. {{variable_name}}', $fixed); + } + + /** + * @covers ::getMainPartName + */ + public function testMainPartNameDetection(): void + { + $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 + */ + public function testGetVariables(): void + { + $templateProcessor = new TestableTemplateProcesor(); + + $variables = $templateProcessor->getVariablesForPart('normal text'); + self::assertEquals([], $variables); + + $variables = $templateProcessor->getVariablesForPart('${documentContent}'); + self::assertEquals(['documentContent'], $variables); + + $variables = $templateProcessor->getVariablesForPart('$15,000.00. ${variable_name}'); + self::assertEquals(['variable_name'], $variables); + } + + /** + * @covers ::getVariables + */ + public function testGetVariablesWithCustomMacro(): void + { + $templateProcessor = new TestableTemplateProcesor(); + $templateProcessor->setMacroOpeningChars('{{'); + $templateProcessor->setMacroClosingChars('}}'); + + $variables = $templateProcessor->getVariablesForPart('normal text'); + self::assertEquals([], $variables); + + $variables = $templateProcessor->getVariablesForPart('{{documentContent}}'); + self::assertEquals(['documentContent'], $variables); + + $variables = $templateProcessor->getVariablesForPart('{15,000.00. {{variable_name}}'); + self::assertEquals(['variable_name'], $variables); + } + + /** + * @covers ::textNeedsSplitting + */ + public function testTextNeedsSplitting(): void + { + $templateProcessor = new TestableTemplateProcesor(); + + self::assertFalse($templateProcessor->textNeedsSplitting('${nothing-to-replace}')); + + $text = 'Hello ${firstname} ${lastname}'; + self::assertTrue($templateProcessor->textNeedsSplitting($text)); + $splitText = $templateProcessor->splitTextIntoTexts($text); + self::assertFalse($templateProcessor->textNeedsSplitting($splitText)); + } + + /** + * @covers ::textNeedsSplitting + */ + public function testTextNeedsSplittingWithCustomMacro(): void + { + $templateProcessor = new TestableTemplateProcesor(); + $templateProcessor->setMacroChars('{{', '}}'); + + self::assertFalse($templateProcessor->textNeedsSplitting('{{nothing-to-replace}}')); + + $text = 'Hello {{firstname}} {{lastname}}'; + self::assertTrue($templateProcessor->textNeedsSplitting($text)); + $splitText = $templateProcessor->splitTextIntoTexts($text); + self::assertFalse($templateProcessor->textNeedsSplitting($splitText)); + } + + /** + * @covers ::splitTextIntoTexts + */ + public function testSplitTextIntoTexts(): void + { + $templateProcessor = new TestableTemplateProcesor(); + + $splitText = $templateProcessor->splitTextIntoTexts('${nothing-to-replace}'); + self::assertEquals('${nothing-to-replace}', $splitText); + + $splitText = $templateProcessor->splitTextIntoTexts('Hello ${firstname} ${lastname}'); + self::assertEquals('Hello ${firstname} ${lastname}', $splitText); + } + + /** + * @covers ::splitTextIntoTexts + */ + public function testSplitTextIntoTextsWithCustomMacro(): void + { + $templateProcessor = new TestableTemplateProcesor(); + $templateProcessor->setMacroChars('{{', '}}'); + + $splitText = $templateProcessor->splitTextIntoTexts('{{nothing-to-replace}}'); + self::assertEquals('{{nothing-to-replace}}', $splitText); + + $splitText = $templateProcessor->splitTextIntoTexts('Hello {{firstname}} {{lastname}}'); + self::assertEquals('Hello {{firstname}} {{lastname}}', $splitText); + } + + public function testFindXmlBlockStart(): void + { + $toFind = ' + + + + + This whole paragraph will be replaced with my ${title} + '; + $mainPart = ' + + + + + + + ${value1} ${value2} + + + + + + + . + + + + + + + + + + ' . $toFind . ' + + '; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $position = $templateProcessor->findContainingXmlBlockForMacro('${title}', 'w:r'); + + self::assertEquals($toFind, $templateProcessor->getSlice($position['start'], $position['end'])); + } + + public function testFindXmlBlockStartWithCustomMacro(): void + { + $toFind = ' + + + + + This whole paragraph will be replaced with my {{title}} + '; + $mainPart = ' + + + + + + + {{value1}} {{value2}} + + + + + + + . + + + + + + + + + + ' . $toFind . ' + + '; + + $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 = ' + + + + + + this is my text containing a ${macro} + + + '; + $templateProcessor = new TestableTemplateProcesor($mainPart); + + //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 testShouldReturnFalseIfXmlBlockNotFoundWithCustomMacro(): void + { + $mainPart = ' + + + + + + this is my text containing a ${macro} + + + '; + $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 = ' + + '; + $templateProcessor = new TestableTemplateProcesor(null, $settingsPart); + + $templateProcessor->setUpdateFields(true); + self::assertStringContainsString('', $templateProcessor->getSettingsPart()); + + $templateProcessor->setUpdateFields(false); + self::assertStringContainsString('', $templateProcessor->getSettingsPart()); + } + + public function testShouldMakeFieldsUpdateOnOpenWithCustomMacro(): void + { + $settingsPart = ' + + '; + $templateProcessor = new TestableTemplateProcesor(null, $settingsPart); + $templateProcessor->setMacroChars('{{', '}}'); + + $templateProcessor->setUpdateFields(true); + self::assertStringContainsString('', $templateProcessor->getSettingsPart()); + + $templateProcessor->setUpdateFields(false); + self::assertStringContainsString('', $templateProcessor->getSettingsPart()); + } + + public function testEnsureUtf8Encoded(): void + { + $mainPart = ' + + + + + + + + ${stringZero} + + + + + + + ${intZero} + + + + + + + ${stringTest} + + + + + + + ${null} + + + + + + + ${floatZero} + + + + + + + ${intTen} + + + + + + + ${boolFalse} + + + + + + + ${boolTrue} + + + + + '; + $templateProcessor = new TestableTemplateProcesor($mainPart); + + self::assertEquals( + ['stringZero', 'intZero', 'stringTest', 'null', 'floatZero', 'intTen', 'boolFalse', 'boolTrue'], + $templateProcessor->getVariables() + ); + + $templateProcessor->setValue('stringZero', '0'); + self::assertStringContainsString('0', $templateProcessor->getMainPart()); + + $templateProcessor->setValue('intZero', 0); + self::assertStringContainsString('0', $templateProcessor->getMainPart()); + + $templateProcessor->setValue('stringTest', 'test'); + self::assertStringContainsString('test', $templateProcessor->getMainPart()); + + $templateProcessor->setValue('null', null); + self::assertStringContainsString('', $templateProcessor->getMainPart()); + + $templateProcessor->setValue('floatZero', 0.00); + self::assertStringContainsString('0', $templateProcessor->getMainPart()); + + $templateProcessor->setValue('intTen', 10); + self::assertStringContainsString('10', $templateProcessor->getMainPart()); + + $templateProcessor->setValue('boolFalse', false); + self::assertStringContainsString('', $templateProcessor->getMainPart()); + + $templateProcessor->setValue('boolTrue', true); + self::assertStringContainsString('1', $templateProcessor->getMainPart()); + } +} diff --git a/tests/PhpWord/Tests/_includes/TestHelperDOCX.php b/tests/PhpWordTests/TestHelperDOCX.php similarity index 67% rename from tests/PhpWord/Tests/_includes/TestHelperDOCX.php rename to tests/PhpWordTests/TestHelperDOCX.php index 2a0043d5c8..2a6fbabae0 100644 --- a/tests/PhpWord/Tests/_includes/TestHelperDOCX.php +++ b/tests/PhpWordTests/TestHelperDOCX.php @@ -1,4 +1,5 @@ save(self::$file); - $zip = new \ZipArchive; + $zip = new ZipArchive(); $res = $zip->open(self::$file); - if ($res === true) { + if (true === $res) { $zip->extractTo(Settings::getTempDir() . '/PhpWord_Unit_Test/'); $zip->close(); } - return new XmlDocument(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + $doc = new XmlDocument(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + if ($writerName === 'ODText') { + $doc->setDefaultFile('content.xml'); + } + + return $doc; } /** - * Clear document + * Clear document. */ - public static function clear() + public static function clear(): void { - if (file_exists(self::$file)) { + 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/'); @@ -82,27 +89,27 @@ public static function clear() } /** - * Delete directory + * Delete directory. * * @param string $dir */ - public static function deleteDir($dir) + public static function deleteDir($dir): void { foreach (scandir($dir) as $file) { - if ($file === '.' || $file === '..') { + if ('.' === $file || '..' === $file) { continue; - } elseif (is_file($dir . "/" . $file)) { - unlink($dir . "/" . $file); - } elseif (is_dir($dir . "/" . $file)) { - self::deleteDir($dir . "/" . $file); + } elseif (is_file($dir . '/' . $file)) { + unlink($dir . '/' . $file); + } elseif (is_dir($dir . '/' . $file)) { + self::deleteDir($dir . '/' . $file); } } - rmdir($dir); + @rmdir($dir); } /** - * Get file + * Get file. * * @return string */ diff --git a/tests/PhpWordTests/TestableTemplateProcesor.php b/tests/PhpWordTests/TestableTemplateProcesor.php new file mode 100644 index 0000000000..9d6eb9904e --- /dev/null +++ b/tests/PhpWordTests/TestableTemplateProcesor.php @@ -0,0 +1,89 @@ +tempDocumentMainPart = $mainPart; + $this->tempDocumentSettingsPart = $settingsPart; + } + + public function fixBrokenMacros($documentPart) + { + return parent::fixBrokenMacros($documentPart); + } + + public function splitTextIntoTexts($text) + { + return parent::splitTextIntoTexts($text); + } + + public function textNeedsSplitting($text) + { + return parent::textNeedsSplitting($text); + } + + public function getVariablesForPart($documentPartXML) + { + $documentPartXML = parent::fixBrokenMacros($documentPartXML); + + return parent::getVariablesForPart($documentPartXML); + } + + public function findXmlBlockStart($offset, $blockType) + { + return parent::findXmlBlockStart($offset, $blockType); + } + + public function findContainingXmlBlockForMacro($macro, $blockType = 'w:p') + { + return parent::findContainingXmlBlockForMacro($macro, $blockType); + } + + public function getSlice($startPosition, $endPosition = 0) + { + return parent::getSlice($startPosition, $endPosition); + } + + /** + * @return string + */ + public function getMainPart() + { + return $this->tempDocumentMainPart; + } + + /** + * @return string + */ + public function getSettingsPart() + { + return $this->tempDocumentSettingsPart; + } +} diff --git a/tests/PhpWordTests/WriteReadback/ODTextTest.php b/tests/PhpWordTests/WriteReadback/ODTextTest.php new file mode 100644 index 0000000000..cee0f38301 --- /dev/null +++ b/tests/PhpWordTests/WriteReadback/ODTextTest.php @@ -0,0 +1,59 @@ +addSection(); + $sectionWriter->addText($testText); + + $writer = new ODText($phpWordWriter); + $file = __DIR__ . '/../_files/temp.odt'; + $writer->save($file); + + self::assertFileExists($file); + + $phpWordReader = IOFactory::load($file, 'ODText'); + + self::assertCount(1, $phpWordReader->getSections()); + self::assertCount(1, $phpWordReader->getSections()[0]->getElements()); + self::assertInstanceOf(TextRun::class, $phpWordReader->getSections()[0]->getElements()[0]); + self::assertEquals($testText, $phpWordReader->getSections()[0]->getElements()[0]->getText()); + unlink($file); + } +} diff --git a/tests/PhpWordTests/WriteReadback/Word2007Test.php b/tests/PhpWordTests/WriteReadback/Word2007Test.php new file mode 100644 index 0000000000..572977ccf8 --- /dev/null +++ b/tests/PhpWordTests/WriteReadback/Word2007Test.php @@ -0,0 +1,170 @@ +setDefaultFontName($testDefaultFontName); + + $writer = new Word2007($phpWordWriter); + $file = __DIR__ . '/../_files/temp.docx'; + $writer->save($file); + + self::assertFileExists($file); + + $phpWordReader = IOFactory::load($file, 'Word2007'); + + self::assertEquals($testDefaultFontName, $phpWordReader->getDefaultFontName()); + + unlink($file); + } + + /** + * Test default Asian font name. + */ + public function testDefaultAsianFontName(): void + { + $phpWordWriter = new PhpWord(); + $testDefaultFontName = '標楷體'; + $phpWordWriter->setDefaultAsianFontName($testDefaultFontName); + + $writer = new Word2007($phpWordWriter); + $file = __DIR__ . '/../_files/temp.docx'; + $writer->save($file); + + self::assertFileExists($file); + + $phpWordReader = IOFactory::load($file, 'Word2007'); + + self::assertEquals($testDefaultFontName, $phpWordReader->getDefaultAsianFontName()); + + unlink($file); + } + + /** + * Test default font size. + */ + public function testDefaulFontSize(): void + { + $phpWordWriter = new PhpWord(); + $testDefaultFontSize = 144; + $phpWordWriter->setDefaultFontSize($testDefaultFontSize); + + $writer = new Word2007($phpWordWriter); + $file = __DIR__ . '/../_files/temp.docx'; + $writer->save($file); + + self::assertFileExists($file); + + $phpWordReader = IOFactory::load($file, 'Word2007'); + + self::assertEquals($testDefaultFontSize, $phpWordReader->getDefaultFontSize()); + + unlink($file); + } + + /** + * Test default font color. + */ + public function testDefaultFontColor(): void + { + $phpWordWriter = new PhpWord(); + $testDefaultFontColor = '00FF00'; + $phpWordWriter->setDefaultFontColor($testDefaultFontColor); + + $writer = new Word2007($phpWordWriter); + $file = __DIR__ . '/../_files/temp.docx'; + $writer->save($file); + + self::assertFileExists($file); + + $phpWordReader = IOFactory::load($file, 'Word2007'); + + self::assertEquals($testDefaultFontColor, $phpWordReader->getDefaultFontColor()); + + unlink($file); + } + + /** + * Test Zoom. + */ + public function testZoom(): void + { + $phpWordWriter = new PhpWord(); + $zoomLevel = 75; + $phpWordWriter->getSettings()->setZoom($zoomLevel); + + $writer = new Word2007($phpWordWriter); + $file = __DIR__ . '/../_files/temp.docx'; + $writer->save($file); + + self::assertFileExists($file); + + $phpWordReader = IOFactory::load($file, 'Word2007'); + + self::assertEquals($zoomLevel, $phpWordReader->getSettings()->getZoom()); + + unlink($file); + } + + /** + * Test a document with one section and text. + */ + public function testOneSectionWithText(): void + { + $phpWordWriter = new PhpWord(); + $testText = 'Hello World!'; + $sectionWriter = $phpWordWriter->addSection(); + $sectionWriter->addText($testText); + + $writer = new Word2007($phpWordWriter); + $file = __DIR__ . '/../_files/temp.docx'; + $writer->save($file); + + self::assertFileExists($file); + + $phpWordReader = IOFactory::load($file, 'Word2007'); + + self::assertCount(1, $phpWordReader->getSections()); + self::assertCount(1, $phpWordReader->getSections()[0]->getElements()); + self::assertInstanceOf(TextRun::class, $phpWordReader->getSections()[0]->getElements()[0]); + self::assertEquals($testText, $phpWordReader->getSections()[0]->getElements()[0]->getText()); + unlink($file); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Element/ImageTest.php b/tests/PhpWordTests/Writer/EPub3/Element/ImageTest.php new file mode 100644 index 0000000000..53685f72dc --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Element/ImageTest.php @@ -0,0 +1,69 @@ +xmlWriter = new XMLWriter(); + $style = new ImageStyle(); + $style->setWidth(100); + $style->setHeight(100); + $this->element = new Image('tests/PhpWordTests/_files/images/earth.jpg', $style); + $this->writer = new ImageWriter($this->xmlWriter, $this->element); + } + + public function testWrite(): void + { + $this->writer->write(); + + $expected = '

    '; + self::assertEquals($expected, $this->xmlWriter->getData()); + } + + public function testWriteWithoutP(): void + { + $style = new ImageStyle(); + $style->setWidth(100); + $style->setHeight(100); + $this->element = new Image('tests/PhpWordTests/_files/images/earth.jpg', $style); + $this->writer = new ImageWriter($this->xmlWriter, $this->element, true); + + $this->writer->write(); + + $expected = ''; + self::assertEquals($expected, $this->xmlWriter->getData()); + } + + public function testWriteWithInvalidElement(): void + { + $invalidElement = $this->createMock(\PhpOffice\PhpWord\Element\AbstractElement::class); + $writer = new ImageWriter($this->xmlWriter, $invalidElement); + + $writer->write(); + + self::assertEquals('', $this->xmlWriter->getData()); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Element/TextTest.php b/tests/PhpWordTests/Writer/EPub3/Element/TextTest.php new file mode 100644 index 0000000000..38490691d5 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Element/TextTest.php @@ -0,0 +1,83 @@ +xmlWriter = new XMLWriter(); + $this->element = new Text('Sample Text'); + $this->writer = new TextWriter($this->xmlWriter, $this->element); + } + + public function testWrite(): void + { + $this->writer->write(); + + $expected = "

    \n Sample Text\n

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

    \n Sample Text\n

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

    \n Sample Text\n

    \n"; + self::assertEquals($expected, $this->xmlWriter->getData()); + } + + public function testWriteWithoutP(): void + { + $text = new Text('Sample Text'); + $xmlWriter = new XMLWriter(); + $this->writer = new TextWriter($xmlWriter, $text, true); + + $this->writer->write(); + + $expected = "Sample Text\n"; + self::assertEquals($expected, $xmlWriter->getData()); + } + + public function testWriteWithInvalidElement(): void + { + $invalidElement = $this->createMock(\PhpOffice\PhpWord\Element\AbstractElement::class); + $writer = new TextWriter($this->xmlWriter, $invalidElement); + + $writer->write(); + + self::assertEquals('', $this->xmlWriter->getData()); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/ElementTest.php b/tests/PhpWordTests/Writer/EPub3/ElementTest.php new file mode 100644 index 0000000000..af745d9176 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/ElementTest.php @@ -0,0 +1,26 @@ +expectException(\PhpOffice\PhpWord\Exception\Exception::class); + + $element = $this->createMock(AbstractElement::class); + WriterElement::getElementClass($element); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartTest.php b/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartTest.php new file mode 100644 index 0000000000..24db959f6e --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartTest.php @@ -0,0 +1,38 @@ +part = $this->getMockForAbstractClass(AbstractPart::class); + } else { + $this->part = new class() extends AbstractPart { + public function write(): string + { + return ''; + } + }; + } + } + + public function testParentWriter(): void + { + $writer = new EPub3(); + $this->part->setParentWriter($writer); + + self::assertInstanceOf(EPub3::class, $this->part->getParentWriter()); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Part/ContentTest.php b/tests/PhpWordTests/Writer/EPub3/Part/ContentTest.php new file mode 100644 index 0000000000..6745ea2eda --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Part/ContentTest.php @@ -0,0 +1,38 @@ +content = new Content($phpWord); + $section = $phpWord->addSection(); + $section->addText('Test content'); + + $writer = new EPub3($phpWord); + $this->content->setParentWriter($writer); + } + + public function testWrite(): void + { + $result = $this->content->write(); + + self::assertIsString($result); + self::assertStringContainsString('', $result); + self::assertStringContainsString('', $result); + self::assertStringContainsString('', $result); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Part/ManifestTest.php b/tests/PhpWordTests/Writer/EPub3/Part/ManifestTest.php new file mode 100644 index 0000000000..d1755da9af --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Part/ManifestTest.php @@ -0,0 +1,35 @@ +manifest = new Manifest(); + $phpWord = new PhpWord(); + $writer = new EPub3($phpWord); + $this->manifest->setParentWriter($writer); + } + + public function testWrite(): void + { + $result = $this->manifest->write(); + + self::assertStringContainsString('', $result); + self::assertIsString($result); + self::assertStringContainsString('', $result); + self::assertStringContainsString('meta = new Meta(); + $phpWord = new PhpWord(); + $writer = new EPub3($phpWord); + $this->meta->setParentWriter($writer); + } + + public function testWrite(): void + { + $result = $this->meta->write(); + + self::assertIsString($result); + self::assertStringContainsString('', $result); + self::assertStringContainsString('getDocInfo(); + $properties->setCreator('PHPWord'); + $properties->setTitle('Test Title'); + $properties->setKeywords('test, keywords'); + + $writer = new EPub3($phpWord); + $this->meta->setParentWriter($writer); + + $expected = '\nTest Titleenurn:uuid:12345PHPWord2023-01-01T00:00:00Z'; + + $result = $this->meta->write(); + + self::assertStringContainsString('PHPWord', $result); + self::assertStringContainsString('Test Title', $result); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Part/MimetypeTest.php b/tests/PhpWordTests/Writer/EPub3/Part/MimetypeTest.php new file mode 100644 index 0000000000..c1cd57f705 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Part/MimetypeTest.php @@ -0,0 +1,32 @@ +mimetype = new Mimetype(); + $phpWord = new PhpWord(); + $writer = new EPub3($phpWord); + $this->mimetype->setParentWriter($writer); + } + + public function testWrite(): void + { + $result = $this->mimetype->write(); + + self::assertIsString($result); + self::assertEquals('application/epub+zip', $result); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Part/NavTest.php b/tests/PhpWordTests/Writer/EPub3/Part/NavTest.php new file mode 100644 index 0000000000..a050f95511 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Part/NavTest.php @@ -0,0 +1,49 @@ +write(); + + // Test that valid XML is generated + $dom = new DOMDocument(); + $dom->loadXML($xml); + + // Test required XML elements and attributes exist + self::assertEquals('html', $dom->documentElement->nodeName); + self::assertEquals('http://www.w3.org/1999/xhtml', $dom->documentElement->getAttribute('xmlns')); + self::assertEquals('http://www.idpf.org/2007/ops', $dom->documentElement->getAttribute('xmlns:epub')); + + // Test nav element + $navElements = $dom->getElementsByTagName('nav'); + self::assertEquals(1, $navElements->length); + $navElement = $navElements->item(0); + self::assertEquals('toc', $navElement->getAttribute('epub:type')); + self::assertEquals('toc', $navElement->getAttribute('id')); + + // Test title exists + $titleElements = $dom->getElementsByTagName('title'); + self::assertEquals(1, $titleElements->length); + self::assertEquals('Navigation', $titleElements->item(0)->nodeValue); + + // Test TOC header exists + $h1Elements = $dom->getElementsByTagName('h1'); + self::assertEquals(1, $h1Elements->length); + self::assertEquals('Table of Contents', $h1Elements->item(0)->nodeValue); + + // Test TOC list structure exists + $olElements = $dom->getElementsByTagName('ol'); + self::assertEquals(1, $olElements->length); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/PartTest.php b/tests/PhpWordTests/Writer/EPub3/PartTest.php new file mode 100644 index 0000000000..ed91c60bcb --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/PartTest.php @@ -0,0 +1,28 @@ +expectException(\PhpOffice\PhpWord\Exception\Exception::class); + + Part::getPartClass('InvalidType'); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php b/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php new file mode 100644 index 0000000000..e06cef8a3b --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php @@ -0,0 +1,35 @@ +getMockForAbstractClass(AbstractStyle::class); + } else { + /** @var AbstractStyle $style */ + $style = new class() extends AbstractStyle { + public function write(): string + { + return ''; + } + }; + } + + $result = $style->setParentWriter($parentWriter); + + self::assertSame($style, $result); + self::assertSame($parentWriter, $style->getParentWriter()); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Style/FontTest.php b/tests/PhpWordTests/Writer/EPub3/Style/FontTest.php new file mode 100644 index 0000000000..26572af3a4 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Style/FontTest.php @@ -0,0 +1,25 @@ +write(); + + self::assertStringContainsString('font-family: "Times New Roman", Times, serif;', $content); + self::assertStringContainsString('font-size: 12pt;', $content); + self::assertStringContainsString('color: #000000;', $content); + self::assertStringStartsWith('body {', $content); + self::assertStringEndsWith('}', $content); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Style/ParagraphTest.php b/tests/PhpWordTests/Writer/EPub3/Style/ParagraphTest.php new file mode 100644 index 0000000000..bcaab0bab1 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Style/ParagraphTest.php @@ -0,0 +1,25 @@ +write(); + + self::assertStringContainsString('margin-top: 0;', $content); + self::assertStringContainsString('margin-bottom: 1em;', $content); + self::assertStringContainsString('text-align: left;', $content); + self::assertStringStartsWith('p {', $content); + self::assertStringEndsWith('}', $content); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/Style/TableTest.php b/tests/PhpWordTests/Writer/EPub3/Style/TableTest.php new file mode 100644 index 0000000000..1d6ff0d6bb --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Style/TableTest.php @@ -0,0 +1,28 @@ +write(); + + self::assertStringContainsString('border-collapse: collapse;', $content); + self::assertStringContainsString('width: 100%;', $content); + self::assertStringContainsString('border: 1px solid black;', $content); + self::assertStringContainsString('padding: 8px;', $content); + self::assertStringContainsString('text-align: left;', $content); + self::assertStringContainsString('table {', $content); + self::assertStringContainsString('th, td {', $content); + self::assertStringEndsWith('}', $content); + } +} diff --git a/tests/PhpWordTests/Writer/EPub3/StyleTest.php b/tests/PhpWordTests/Writer/EPub3/StyleTest.php new file mode 100644 index 0000000000..15db0d581d --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/StyleTest.php @@ -0,0 +1,23 @@ +setXmlWriter($xmlWriter); + $object->write(); + + self::assertEquals('', $xmlWriter->getData()); + } + } +} diff --git a/tests/PhpWordTests/Writer/EPub3Test.php b/tests/PhpWordTests/Writer/EPub3Test.php new file mode 100644 index 0000000000..acdbb5e32f --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3Test.php @@ -0,0 +1,126 @@ +getPhpWord()); + self::assertEquals('./', $object->getDiskCachingDirectory()); + foreach (['Content', 'Manifest', 'Mimetype'] as $part) { + self::assertInstanceOf( + "PhpOffice\\PhpWord\\Writer\\Epub3\\Part\\{$part}", + $object->getWriterPart($part) + ); + self::assertInstanceOf( + 'PhpOffice\\PhpWord\\Writer\\Epub3', + $object->getWriterPart($part)->getParentWriter() + ); + } + } + + /** + * Test construction with null. + */ + public function testConstructWithNull(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('No PhpWord assigned.'); + + $writer = new EPub3(); + $writer->getWriterPart('content')->write(); + } + + /** + * Test saving document. + */ + public function testSave(): void + { + $imageSrc = __DIR__ . '/../_files/images/PhpWord.png'; + $file = __DIR__ . '/../_files/temp.epub'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Test 1'); + $section->addTextBreak(); + $section->addText('Test 2', null, ['alignment' => Jc::CENTER]); + $section->addLink('https://github.com/PHPOffice/PHPWord'); + $section->addTitle('Test', 1); + $section->addPageBreak(); + $section->addImage($imageSrc); + $writer = new EPub3($phpWord); + $writer->save($file); + self::assertFileExists($file); + unlink($file); + } + + /** + * Test PHP output. + */ + public function testSavePhpOutput(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Test'); + $writer = new EPub3($phpWord); + ob_start(); + $writer->save('php://output'); + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); + self::assertNotEmpty($contents); + } + + /** + * Test disk caching. + */ + public function testSetGetUseDiskCaching(): void + { + $object = new EPub3(); + $object->setUseDiskCaching(true, PHPWORD_TESTS_BASE_DIR); + self::assertTrue($object->isUseDiskCaching()); + self::assertEquals(PHPWORD_TESTS_BASE_DIR, $object->getDiskCachingDirectory()); + } + + /** + * Test disk caching exception. + */ + public function testSetUseDiskCachingException(): void + { + $this->expectException(Exception::class); + $dir = implode(DIRECTORY_SEPARATOR, [PHPWORD_TESTS_BASE_DIR, 'foo']); + + $object = new EPub3(); + $object->setUseDiskCaching(true, $dir); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/DirectionTest.php b/tests/PhpWordTests/Writer/HTML/DirectionTest.php new file mode 100644 index 0000000000..f3b5830d28 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/DirectionTest.php @@ -0,0 +1,58 @@ +addSection(); + $html = '

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

    '; + SharedHtml::addHtml($section, $html, false, false); + $english = '

    LTR in RTL document.

    '; + 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, '')); + self::assertSame(2, substr_count($content, '')); + self::assertSame(3, substr_count($content, '

    ')); + self::assertSame(2, substr_count($content, '

    ')); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php b/tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php new file mode 100644 index 0000000000..23f5890e9d --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php @@ -0,0 +1,74 @@ + ' . 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('', $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('', $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('
    ', $object->write()); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php b/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php new file mode 100644 index 0000000000..2ca556bc51 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php @@ -0,0 +1,99 @@ +addSection(); + $properties = new RubyProperties(); + $properties->setAlignment(RubyProperties::ALIGNMENT_CENTER); + $properties->setFontFaceSize(10); + $properties->setFontPointsAboveBaseText(4); + $properties->setFontSizeForBaseText(20); + $properties->setLanguageId('ja-JP'); + + $baseTextRun = new TextRun(null); + $baseTextRun->addText('私'); + $rubyTextRun = new TextRun(null); + $rubyTextRun->addText('わたし'); + $section->addRuby($baseTextRun, $rubyTextRun, $properties); + + $dom = Helper::getAsHTML($phpWord, '', '', ['ruby', 'rt', 'rp']); + $xpath = new DOMXPath($dom); + self::assertEquals(1, $xpath->query('/html/body/div/ruby')->length); + // ensure text is right + $rubyElement = $dom->getElementsByTagName('ruby')->item(0); + $rtElement = $dom->getElementsByTagName('rt')->item(0); + self::assertNotNull($rubyElement); + self::assertNotNull($rtElement); + self::assertEquals($baseTextRun->getText() . ' (' . $rubyTextRun->getText() . ')', $rubyElement->textContent); + self::assertEquals($rubyTextRun->getText(), $rtElement->textContent); + // check style + self::assertEquals('font-size:20pt;ruby-align:center;', $rubyElement->attributes->getNamedItem('style')->textContent); + self::assertEquals('font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); + } + + /** + * Tests writing ruby HTML. + */ + public function testWriteRubyHtmlParagraphStyle(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $properties = new RubyProperties(); + $properties->setAlignment(RubyProperties::ALIGNMENT_CENTER); + $properties->setFontFaceSize(10); + $properties->setFontPointsAboveBaseText(4); + $properties->setFontSizeForBaseText(20); + $properties->setLanguageId('ja-JP'); + + $baseTextRun = new TextRun(['lineHeight' => '8']); + $baseTextRun->addText('私'); + $rubyTextRun = new TextRun(['lineHeight' => '4']); + $rubyTextRun->addText('わたし'); + $section->addRuby($baseTextRun, $rubyTextRun, $properties); + + $dom = Helper::getAsHTML($phpWord, '', '', ['ruby', 'rt', 'rp']); + $xpath = new DOMXPath($dom); + self::assertEquals(1, $xpath->query('/html/body/div/ruby')->length); + // ensure text is right + $rubyElement = $dom->getElementsByTagName('ruby')->item(0); + $rtElement = $dom->getElementsByTagName('rt')->item(0); + self::assertNotNull($rubyElement); + self::assertNotNull($rtElement); + self::assertEquals($baseTextRun->getText() . ' (' . $rubyTextRun->getText() . ')', $rubyElement->textContent); + self::assertEquals($rubyTextRun->getText(), $rtElement->textContent); + // check style + self::assertEquals('line-height: 8;font-size:20pt;ruby-align:center;', $rubyElement->attributes->getNamedItem('style')->textContent); + self::assertEquals('line-height: 4;font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/Element/TableTest.php b/tests/PhpWordTests/Writer/HTML/Element/TableTest.php new file mode 100644 index 0000000000..cd0bafaab0 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Element/TableTest.php @@ -0,0 +1,235 @@ +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/Element/TextTest.php b/tests/PhpWordTests/Writer/HTML/Element/TextTest.php new file mode 100644 index 0000000000..2dfdd53be3 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Element/TextTest.php @@ -0,0 +1,43 @@ + 

    ' . PHP_EOL, $object->write()); + } + + public function testHTMLEmptyString(): void + { + $writer = new HTML(); + $object = new Text($writer, new BaseText('')); + + self::assertEquals('

     

    ' . PHP_EOL, $object->write()); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/ElementTest.php b/tests/PhpWordTests/Writer/HTML/ElementTest.php new file mode 100644 index 0000000000..3b2580381f --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/ElementTest.php @@ -0,0 +1,246 @@ +write()); + } + } + + /** + * Test write element text. + */ + public function testWriteTextElement(): void + { + $object = new Text(new HTML(), new TextElement(htmlspecialchars('A', ENT_COMPAT, 'UTF-8'))); + $object->setOpeningText(htmlspecialchars('-', ENT_COMPAT, 'UTF-8')); + $object->setClosingText(htmlspecialchars('-', ENT_COMPAT, 'UTF-8')); + $object->setWithoutP(true); + + self::assertEquals(htmlspecialchars('-A-', ENT_COMPAT, 'UTF-8'), $object->write()); + } + + /** + * Test write TrackChange. + */ + public function testWriteTrackChanges(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $text = $section->addText('my dummy text'); + $text->setChangeInfo(TrackChange::INSERTED, 'author name'); + $text2 = $section->addText('my other text'); + $text2->setTrackChange(new TrackChange(TrackChange::DELETED, 'another author', new DateTime())); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals(1, $xpath->query('/html/body/div/p[1]/ins')->length); + self::assertEquals(1, $xpath->query('/html/body/div/p[2]/del')->length); + } + + /** + * Tests writing table with col span. + */ + public function testWriteColSpan(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $table = $section->addTable(); + $row1 = $table->addRow(); + $cell11 = $row1->addCell(1000, ['gridSpan' => 2, 'bgColor' => '6086B8']); + $cell11->addText('cell spanning 2 bellow'); + $row2 = $table->addRow(); + $cell21 = $row2->addCell(500, ['bgColor' => 'ffffff']); + $cell21->addText('first cell'); + $cell22 = $row2->addCell(500); + $cell22->addText('second cell'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals(1, $xpath->query('/html/body/div/table/tr[1]/td')->length); + self::assertEquals('2', $xpath->query('/html/body/div/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); + self::assertEquals(2, $xpath->query('/html/body/div/table/tr[2]/td')->length); + + 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')); + } + + /** + * Tests writing table with row span. + */ + public function testWriteRowSpan(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $table = $section->addTable(); + + $row1 = $table->addRow(); + $row1->addCell(1000, ['vMerge' => 'restart'])->addText('row spanning 3 bellow'); + $row1->addCell(500)->addText('first cell being spanned'); + + $row2 = $table->addRow(); + $row2->addCell(null, ['vMerge' => 'continue']); + $row2->addCell(500)->addText('second cell being spanned'); + + $row3 = $table->addRow(); + $row3->addCell(null, ['vMerge' => 'continue']); + $row3->addCell(500)->addText('third cell being spanned'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals(2, $xpath->query('/html/body/div/table/tr[1]/td')->length); + self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[1]')->item(0)->attributes->getNamedItem('rowspan')->textContent); + self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length); + } + + /** + * Tests writing table with rowspan and colspan. + */ + public function testWriteRowSpanAndColSpan(): void + { + $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); + + self::assertEquals(3, $xpath->query('/html/body/div/table/tr[3]/td')->length); + } + + public function testWriteTitleTextRun(): void + { + $expected = 'Title with TextRun'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $textRun = new TextRun(); + $textRun->addText($expected); + + $section->addTitle($textRun); + + $htmlWriter = new HTML($phpWord); + $content = $htmlWriter->getContent(); + + self::assertStringContainsString($expected, $content); + } + + /** + * Test write element ListItemRun. + */ + public function testListItemRun(): void + { + $expected1 = 'List item run 1'; + $expected2 = 'List item run 1 in bold'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $listItemRun = $section->addListItemRun(0, null, 'MyParagraphStyle'); + $listItemRun->addText($expected1); + $listItemRun->addText($expected2, ['bold' => true]); + + $htmlWriter = new HTML($phpWord); + $content = $htmlWriter->getContent(); + + $dom = new DOMDocument(); + $dom->loadHTML($content); + + self::assertEquals($expected1, $dom->getElementsByTagName('p')->item(0)->textContent); + self::assertEquals($expected2, $dom->getElementsByTagName('p')->item(1)->textContent); + } + + /** + * Tests writing table with layout. + */ + public function testWriteTableLayout(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addTable(); + + $table1 = $section->addTable(['layout' => \PhpOffice\PhpWord\Style\Table::LAYOUT_FIXED]); + $row1 = $table1->addRow(); + $row1->addCell()->addText('fixed layout table'); + + $table2 = $section->addTable(['layout' => \PhpOffice\PhpWord\Style\Table::LAYOUT_AUTO]); + $row2 = $table2->addRow(); + $row2->addCell()->addText('auto layout table'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals('table-layout: fixed;', $xpath->query('/html/body/div/table[1]')->item(0)->attributes->getNamedItem('style')->textContent); + self::assertEquals('table-layout: auto;', $xpath->query('/html/body/div/table[2]')->item(0)->attributes->getNamedItem('style')->textContent); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/FontTest.php b/tests/PhpWordTests/Writer/HTML/FontTest.php new file mode 100644 index 0000000000..0a203b7237 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/FontTest.php @@ -0,0 +1,356 @@ +defaultFontName = Settings::getDefaultFontName(); + $this->defaultFontSize = Settings::getDefaultFontSize(); + $this->defaultFontColor = Settings::getDefaultFontColor(); + } + + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + Settings::setDefaultFontName($this->defaultFontName); + Settings::setDefaultFontSize($this->defaultFontSize); + Settings::setDefaultFontColor($this->defaultFontColor); + } + + public function testDefaultDefaults(): void + { + $phpWord = new PhpWord(); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + $style = Helper::getTextContent($xpath, '/html/head/style[1]'); + + $prg = preg_match('/body {(.*?)}/', $style, $matches); + self::assertNotEmpty($matches); + self::assertNotFalse($prg); + self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; color: #000000;}', $matches[0]); + } + + public function testSettingDefaultFontColor(): void + { + $phpWord = new PhpWord(); + + $defaultFontColor = '00FF00'; + $phpWord->setDefaultFontColor($defaultFontColor); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + $style = Helper::getTextContent($xpath, '/html/head/style[1]'); + + $prg = preg_match('/body {(.*?)}/', $style, $matches); + self::assertNotEmpty($matches); + self::assertNotFalse($prg); + self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; color: #00FF00;}', $matches[0]); + } + + /** + * 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::assertNotEmpty($matches); + self::assertNotFalse($prg); + self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt; color: #000000;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotEmpty($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::assertNotEmpty($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::assertNotEmpty($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::assertNotEmpty($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::assertNotEmpty($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::assertNotEmpty($matches); + self::assertNotFalse($prg); + self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt; color: #000000;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotEmpty($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::assertNotEmpty($matches); + self::assertNotFalse($prg); + self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); + self::assertNotEmpty($matches); + self::assertNotFalse($prg); + self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); + self::assertNotEmpty($matches); + self::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::assertNotEmpty($matches); + self::assertNotFalse($prg); + self::assertEquals('* {font-family: \'Courier New\', monospace; font-size: 12pt; color: #000000;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotEmpty($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::assertNotEmpty($matches); + self::assertNotFalse($prg); + self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); + self::assertNotEmpty($matches); + self::assertNotFalse($prg); + self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); + self::assertNotEmpty($matches); + self::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; color: #000000; white-space: pre-wrap;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotEmpty($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::assertNotEmpty($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::assertNotEmpty($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::assertNotEmpty($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..37f640d28a --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Helper.php @@ -0,0 +1,128 @@ +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 = '', array $validTags = []): DOMDocument + { + $htmlWriter = new HTML($phpWord); + $htmlWriter->setDefaultWhiteSpace($defaultWhiteSpace); + $htmlWriter->setDefaultGenericFont($defaultGenericFont); + $dom = new DOMDocument(); + // DOMDocument does not always accept HTML5 tags like + // So, we can manually filter out those errors for testing purposes ONLY. + $original = libxml_use_internal_errors(true); + $dom->loadHTML($htmlWriter->getContent()); + $errors = libxml_get_errors(); + $errorsToReport = []; + foreach ($errors as $error) { + /** @var LibXMLError $error */ + if ($error->code === 801) { + $didFindValidTag = false; + foreach ($validTags as $tag) { + if (trim($error->message) === ('Tag ' . $tag . ' invalid')) { + $didFindValidTag = true; + + break; + } + } + if (!$didFindValidTag) { + $errorsToReport[] = $error; + } + } else { + $errorsToReport[] = $error; + } + } + libxml_clear_errors(); + libxml_use_internal_errors($original); + if (count($errorsToReport) > 0) { + throw new Exception('Errors when loading DOMDocument: ' . print_r($errors, true)); + } + + return $dom; + } +} diff --git a/tests/PhpWordTests/Writer/HTML/ParagraphTest.php b/tests/PhpWordTests/Writer/HTML/ParagraphTest.php new file mode 100644 index 0000000000..2b2724dba8 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/ParagraphTest.php @@ -0,0 +1,138 @@ + 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('

    Text before blank text

    ', $bodylines[2]); + self::assertEquals('

     

    ', $bodylines[3]); + self::assertEquals('

    Text after blank text

    ', $bodylines[4]); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/PartTest.php b/tests/PhpWordTests/Writer/HTML/PartTest.php new file mode 100644 index 0000000000..b6748a58c5 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/PartTest.php @@ -0,0 +1,189 @@ +expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $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/PhpWord/Tests/Writer/HTML/StyleTest.php b/tests/PhpWordTests/Writer/HTML/StyleTest.php similarity index 67% rename from tests/PhpWord/Tests/Writer/HTML/StyleTest.php rename to tests/PhpWordTests/Writer/HTML/StyleTest.php index 8af1e47930..6dcb12f630 100644 --- a/tests/PhpWord/Tests/Writer/HTML/StyleTest.php +++ b/tests/PhpWordTests/Writer/HTML/StyleTest.php @@ -1,4 +1,5 @@ assertEquals('', $object->write()); + self::assertEquals('', $object->write()); } } } diff --git a/tests/PhpWordTests/Writer/HTMLTest.php b/tests/PhpWordTests/Writer/HTMLTest.php new file mode 100644 index 0000000000..cedd9f2f32 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTMLTest.php @@ -0,0 +1,188 @@ +getPhpWord()); + } + + /** + * Construct with null. + */ + public function testConstructWithNull(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectExceptionMessage('No PhpWord assigned.'); + $object = new HTML(); + $object->getPhpWord(); + } + + public function testEditCallback(): void + { + $object = new HTML(new PhpWord()); + + self::assertNull($object->getEditCallback()); + + $object->setEditCallback(function (string $html): string { + return $html; + }); + self::assertIsCallable($object->getEditCallback()); + + $object->setEditCallback(null); + self::assertNull($object->getEditCallback()); + } + + public function testDefaultGenericFont(): void + { + $object = new HTML(new PhpWord()); + + self::assertEquals('', $object->getDefaultGenericFont()); + + $object->setDefaultGenericFont('test'); + self::assertEquals('', $object->getDefaultGenericFont()); + + $object->setDefaultGenericFont('cursive'); + self::assertEquals('cursive', $object->getDefaultGenericFont()); + } + + public function testDefaultWhiteSpace(): void + { + $object = new HTML(new PhpWord()); + + self::assertEquals('', $object->getDefaultWhiteSpace()); + + $object->setDefaultWhiteSpace('test'); + self::assertEquals('', $object->getDefaultWhiteSpace()); + + $object->setDefaultWhiteSpace('pre-line'); + self::assertEquals('pre-line', $object->getDefaultWhiteSpace()); + } + + /** + * Save. + */ + public function testSave(): void + { + $localImage = __DIR__ . '/../_files/images/PhpWord.png'; + $archiveImage = 'zip://' . __DIR__ . '/../_files/documents/reader.docx#word/media/image1.jpeg'; + $gdImage = self::getRemoteGifImageUrl(); + $objectSrc = __DIR__ . '/../_files/documents/sheet.xls'; + $file = __DIR__ . '/../_files/temp.html'; + + $phpWord = new PhpWord(); + + $docProps = $phpWord->getDocInfo(); + $docProps->setTitle(htmlspecialchars('HTML Test', ENT_COMPAT, 'UTF-8')); + + $phpWord->addTitleStyle(1, ['bold' => true]); + $phpWord->addFontStyle( + 'Font', + ['name' => 'Verdana', 'size' => 11, 'color' => 'FF0000', 'fgColor' => 'FF0000'] + ); + $phpWord->addParagraphStyle('Paragraph', ['alignment' => Jc::CENTER, 'spaceAfter' => 20, 'spaceBefore' => 20]); + $section = $phpWord->addSection(); + $section->addBookmark('top'); + $section->addText(htmlspecialchars('Test 1', ENT_COMPAT, 'UTF-8'), 'Font', 'Paragraph'); + $section->addTextBreak(); + $section->addText( + htmlspecialchars('Test 2', ENT_COMPAT, 'UTF-8'), + ['name' => 'Tahoma', 'bold' => true, 'italic' => true, 'subscript' => true] + ); + $section->addLink('https://github.com/PHPOffice/PHPWord'); + $section->addTitle(htmlspecialchars('Test', ENT_COMPAT, 'UTF-8'), 1); + $section->addPageBreak(); + $section->addListItem(htmlspecialchars('Test', ENT_COMPAT, 'UTF-8')); + $section->addImage($localImage); + $section->addImage($archiveImage); + $section->addImage($gdImage); + $section->addObject($objectSrc); + $section->addFootnote(); + $section->addEndnote(); + + $section = $phpWord->addSection(); + + $textrun = $section->addTextRun(['alignment' => Jc::CENTER]); + $textrun->addText(htmlspecialchars('Test 3', ENT_COMPAT, 'UTF-8')); + $textrun->addTextBreak(); + + $textrun = $section->addTextRun(['alignment' => Jc::START]); + $textrun->addText(htmlspecialchars('Text left aligned', ENT_COMPAT, 'UTF-8')); + + $textrun = $section->addTextRun(['alignment' => Jc::BOTH]); + $textrun->addText(htmlspecialchars('Text justified', ENT_COMPAT, 'UTF-8')); + + $textrun = $section->addTextRun(['alignment' => Jc::END]); + $textrun->addText(htmlspecialchars('Text right aligned', ENT_COMPAT, 'UTF-8')); + + $textrun = $section->addTextRun('Paragraph'); + $textrun->addLink('https://github.com/PHPOffice/PHPWord'); + $textrun->addImage($localImage); + $textrun->addFootnote()->addText(htmlspecialchars('Footnote', ENT_COMPAT, 'UTF-8')); + $textrun->addEndnote()->addText(htmlspecialchars('Endnote', ENT_COMPAT, 'UTF-8')); + + $section = $phpWord->addSection(); + + $table = $section->addTable(); + $cell = $table->addRow()->addCell(); + $cell->addText( + htmlspecialchars('Test 1', ENT_COMPAT, 'UTF-8'), + ['superscript' => true, 'underline' => 'dash', 'strikethrough' => true] + ); + $cell->addTextRun(); + $cell->addLink('https://github.com/PHPOffice/PHPWord'); + $cell->addTextBreak(); + $cell->addListItem(htmlspecialchars('Test', ENT_COMPAT, 'UTF-8')); + $cell->addImage($localImage); + $cell->addObject($objectSrc); + $cell->addFootnote(); + $cell->addEndnote(); + $cell = $table->addRow()->addCell(); + $section->addLink('top', 'back to top', null, null, true); + + $writer = new HTML($phpWord); + + $writer->save($file); + self::assertFileExists($file); + unlink($file); + + Settings::setOutputEscapingEnabled(true); + $writer->save($file); + self::assertFileExists($file); + unlink($file); + } +} diff --git a/tests/PhpWordTests/Writer/ODText/Element/FieldTest.php b/tests/PhpWordTests/Writer/ODText/Element/FieldTest.php new file mode 100644 index 0000000000..0f936d053e --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Element/FieldTest.php @@ -0,0 +1,65 @@ +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..c834a465f0 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Element/FormulaTest.php @@ -0,0 +1,72 @@ +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 new file mode 100644 index 0000000000..ff7788df26 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Element/ImageTest.php @@ -0,0 +1,128 @@ +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('', $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('end', $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::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..9f531255fc --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Element/ListItemRunTest.php @@ -0,0 +1,83 @@ +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 new file mode 100644 index 0000000000..8ca327717c --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/ElementTest.php @@ -0,0 +1,367 @@ +write(); + + self::assertEquals('', $xmlWriter->getData()); + } + } + + // ODT Line Element not yet implemented + // ODT Bookmark not yet implemented + // ODT Table with style name not yet implemented (Word test defective) + // ODT Shape Elements not yet implemented + // ODT Chart Elements not yet implemented + // ODT adding Field to Section not yet implemented + // ODT List not yet implemented + // ODT Macro Button not yet implemented + // ODT Form Field not yet implemented + // ODT SDT not yet implemented + // ODT Comment not yet implemented + // ODT Track Changes implemented, possibly not correctly + // ODT List Item not yet implemented + + /** + * Test link element. + */ + public function testLinkElement(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $extlink = 'https://github.com/PHPOffice/PHPWord'; + $section->addLink($extlink); + $intlink = 'internal_link'; + $section->addLink($intlink, null, null, null, true); + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $p2t = '/office:document-content/office:body/office:text/text:section'; + $element = "$p2t/text:p[2]/text:a"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals($extlink, $doc->getElementAttribute($element, 'xlink:href')); + + $element = "$p2t/text:p[3]/text:a"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals("#$intlink", $doc->getElementAttribute($element, 'xlink:href')); + } + + /** + * Basic test for table element. + */ + public function testTableElements(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $table = $section->addTable(['alignment' => \PhpOffice\PhpWord\SimpleType\JcTable::CENTER]); + $table->addRow(900); + $table->addCell(2000)->addText('Row 1'); + $table->addCell(2000)->addText('Row 2'); + $table->addCell(2000)->addText('Row 3'); + $table->addCell(2000)->addText('Row 4'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $p2s = '/office:document-content/office:automatic-styles'; + $tableStyleNum = 1; + /** @var null|string $tableStyleName */ + $tableStyleName = null; + $element = ''; + while ($tableStyleName === null) { + $element = "$p2s/style:style[$tableStyleNum]"; + if (!$doc->elementExists($element)) { + break; + } + if ($doc->getElementAttribute($element, 'style:family') === 'table') { + $tableStyleName = $doc->getElementAttribute($element, 'style:name'); + + break; + } + ++$tableStyleNum; + } + self::assertNotNull($tableStyleName); + $element = "$element/style:table-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals(\PhpOffice\PhpWord\SimpleType\JcTable::CENTER, $doc->getElementAttribute($element, 'table:align')); + $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-name')); + self::assertTrue($doc->elementExists($tableRootElement . '/table:table-column[4]')); + } + + /** + * 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, 'ODText'); + + $p2t = '/office:document-content/office:body/office:text/text:section'; + $element = "$p2t/text:h[1]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('HE0', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('0', $doc->getElementAttribute($element, 'text:outline-level')); + $span = "$element/text:span"; + self::assertTrue($doc->elementExists($span)); + self::assertEquals('This is a title', $doc->getElement($span)->textContent); + self::assertEquals('Title', $doc->getElementAttribute($span, 'text:style-name')); + + $element = "$p2t/text:h[2]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('HD1', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $span = "$element/text:span"; + self::assertTrue($doc->elementExists($span)); + self::assertEquals('Heading 1', $doc->getElement($span)->textContent); + self::assertEquals('Heading_1', $doc->getElementAttribute($span, 'text:style-name')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style[1]'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('Title', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:text-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('14pt', $doc->getElementAttribute($element, 'fo:font-size')); + self::assertEquals('italic', $doc->getElementAttribute($element, 'fo:font-style')); + self::assertEquals('', $doc->getElementAttribute($element, 'fo:font-weight')); + self::assertEquals('', $doc->getElementAttribute($element, 'fo:color')); + + $element = '/office:document-styles/office:styles/style:style[2]'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('Heading_1', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:text-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('20pt', $doc->getElementAttribute($element, 'fo:font-size')); + self::assertEquals('', $doc->getElementAttribute($element, 'fo:font-style')); + self::assertEquals('bold', $doc->getElementAttribute($element, 'fo:font-weight')); + self::assertEquals('#333333', $doc->getElementAttribute($element, 'fo:color')); + } + + /** + * Test title specified as text run rather than text. + */ + public function testTextRunTitle(): void + { + $phpWord = new PhpWord(); + $phpWord->addTitleStyle(1, ['name' => 'Times New Roman', 'size' => 18, 'bold' => true]); + $section = $phpWord->addSection(); + $section->addTitle('Text Title', 1); + $section->addText('Text following Text Title'); + $textRun = new TextRun(); + $textRun->addText('Text Run'); + $textRun->addText(' Title'); + $section->addTitle($textRun, 1); + $section->addText('Text following Text Run Title'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $p2t = '/office:document-content/office:body/office:text/text:section'; + + $element = "$p2t/text:h[1]"; + self::assertEquals('HE1', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $span = "$element/text:span"; + self::assertEquals('Text Title', $doc->getElement($span)->textContent); + self::assertEquals('Heading_1', $doc->getElementAttribute($span, 'text:style-name')); + $element = "$p2t/text:p[2]/text:span"; + self::assertEquals('Text following Text Title', $doc->getElement($element)->nodeValue); + + $element = "$p2t/text:h[2]"; + self::assertEquals('HD1', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $span = "$element/text:span"; + self::assertEquals('Text Run', $doc->getElement("$span/text:span[1]")->textContent); + self::assertTrue($doc->elementExists("$span/text:span[2]/text:s")); + self::assertEquals('Title', $doc->getElement("$span/text:span[2]")->textContent); + self::assertEquals('Heading_1', $doc->getElementAttribute($span, 'text:style-name')); + $element = "$p2t/text:p[3]/text:span"; + self::assertEquals('Text following Text Run Title', $doc->getElement($element)->nodeValue); + } + + /** + * Test correct writing of text with ampersand in it. + */ + public function testTextWithAmpersand(): void + { + $esc = \PhpOffice\PhpWord\Settings::isOutputEscapingEnabled(); + \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $txt = 'this text contains an & (ampersand)'; + $section->addText($txt); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled($esc); + $p2t = '/office:document-content/office:body/office:text/text:section'; + $element = "$p2t/text:p[2]"; + self::assertTrue($doc->elementExists($element)); + $span = "$element/text:span"; + self::assertTrue($doc->elementExists($span)); + self::assertEquals($txt, $doc->getElement($span)->nodeValue); + } + + /** + * Test PageBreak. + */ + public function testPageBreak(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('test'); + $section->addPageBreak(); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $element = '/office:document-content/office:body/office:text/text:section/text:p[3]'; + self::assertTrue($doc->elementExists($element, 'content.xml')); + self::assertEquals('PB', $doc->getElementAttribute($element, 'text:style-name', 'content.xml')); + } + + /** + * Test tracked changes. + */ + public function testTrackedChanges(): void + { + $phpWord = new PhpWord(); + + // New portrait section + $section = $phpWord->addSection(); + $textRun = $section->addTextRun(); + + $text = $textRun->addText('Hello World! Time to '); + + $text = $textRun->addText('wake ', ['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))); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $tcs = '/office:document-content/office:body/office:text/text:tracked-changes'; + $tc1 = "$tcs/text:changed-region[1]"; + $tc1id = $doc->getElementAttribute($tc1, 'text:id'); + $element = "$tc1/text:insertion"; + self::assertTrue($doc->elementExists($element)); + $element .= '/office:change-info'; + self::AssertEquals('Fred', $doc->getElement("$element/dc:creator")->nodeValue); + self::assertTrue($doc->elementExists("$element/dc:date")); + + $tc2 = "$tcs/text:changed-region[2]"; + $tc2id = $doc->getElementAttribute($tc2, 'text:id'); + $element = "$tc2/text:insertion"; + self::assertTrue($doc->elementExists($element)); + $element .= '/office:change-info'; + self::AssertEquals('Fred', $doc->getElement("$element/dc:creator")->nodeValue); + //self::assertTrue($doc->elementExists("$element/dc:date")); + + $tc3 = "$tcs/text:changed-region[3]"; + $tc3id = $doc->getElementAttribute($tc3, 'text:id'); + $element = "$tc3/text:deletion"; + self::assertTrue($doc->elementExists($element)); + $element .= '/office:change-info'; + self::AssertEquals('Barney', $doc->getElement("$element/dc:creator")->nodeValue); + self::assertTrue($doc->elementExists("$element/dc:date")); + + $p2t = '/office:document-content/office:body/office:text/text:section/text:p[2]'; + $element = "$p2t/text:span[2]/text:change-start"; + self::AssertEquals($tc1id, $doc->getElementAttribute($element, 'text:change-id')); + $element = "$p2t/text:span[3]/text:change-start"; + self::AssertEquals($tc2id, $doc->getElementAttribute($element, 'text:change-id')); + $element = "$p2t/text:change"; + self::AssertEquals($tc3id, $doc->getElementAttribute($element, 'text:change-id')); + } + + /** + * Test ruby output. + * Note that this test will need to be updated when ODT Ruby output supports + * ODT's native ruby functionality. + */ + public function testRubyText(): void + { + $esc = \PhpOffice\PhpWord\Settings::isOutputEscapingEnabled(); + \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $properties = new RubyProperties(); + $properties->setAlignment(RubyProperties::ALIGNMENT_RIGHT_VERTICAL); + $properties->setFontFaceSize(10); + $properties->setFontPointsAboveBaseText(4); + $properties->setFontSizeForBaseText(18); + $properties->setLanguageId('ja-JP'); + + $baseTextRun = new TextRun(null); + $baseTextRun->addText('私'); + $rubyTextRun = new TextRun(null); + $rubyTextRun->addText('わたし'); + $section->addRuby($baseTextRun, $rubyTextRun, $properties); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled($esc); + $p2t = '/office:document-content/office:body/office:text/text:section'; + $element = "$p2t/text:p[2]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('私 (わたし)', $doc->getElement($element)->nodeValue); + } +} diff --git a/tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php b/tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php new file mode 100644 index 0000000000..049b9e7cb4 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php @@ -0,0 +1,74 @@ +getMockForAbstractClass(ODText\Part\AbstractPart::class); + } else { + /** @var ODText\Part\AbstractPart $object */ + $object = new class() extends ODText\Part\AbstractPart { + public function write(): string + { + return ''; + } + }; + } + $object->setParentWriter(new ODText()); + self::assertEquals(new ODText(), $object->getParentWriter()); + } + + /** + * covers ::getParentWriter. + */ + public function testSetGetParentWriterNull(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('No parent WriterInterface assigned.'); + // @phpstan-ignore-next-line + if (method_exists($this, 'getMockForAbstractClass')) { + $object = $this->getMockForAbstractClass(ODText\Part\AbstractPart::class); + } else { + /** @var ODText\Part\AbstractPart $object */ + $object = new class() extends ODText\Part\AbstractPart { + public function write(): string + { + return ''; + } + }; + } + $object->getParentWriter(); + } +} diff --git a/tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php b/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php similarity index 56% rename from tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php rename to tests/PhpWordTests/Writer/ODText/Part/ContentTest.php index 85ddada6ba..e1dc78a32b 100644 --- a/tests/PhpWord/Tests/Writer/ODText/Part/ContentTest.php +++ b/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php @@ -1,4 +1,5 @@ setCustomProperty('Company', 'PHPWord'); $phpWord->setDefaultFontName('Verdana'); - $phpWord->addFontStyle('Font', array('size' => 11)); - $phpWord->addParagraphStyle('Paragraph', array('align' => 'center')); - $phpWord->addTableStyle('tblStyle', array('width' => 100)); + $phpWord->addFontStyle('Font', ['size' => 11]); + $phpWord->addParagraphStyle('Paragraph', ['alignment' => Jc::CENTER]); + $phpWord->addTableStyle('tblStyle', ['width' => 100]); - $section = $phpWord->addSection(array('colsNum' => 2)); + $section = $phpWord->addSection(['colsNum' => 2]); $section->addText($expected); $section->addText('Test font style', 'Font'); $section->addText('Test paragraph style', null, 'Paragraph'); - $section->addLink('http://test.com/', 'Test link'); + $section->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord on GitHub'); $section->addTitle('Test title', 1); $section->addTextBreak(); $section->addPageBreak(); $section->addListItem('Test list item'); - $section->addImage($imageSrc, array('width' => 50)); + $section->addImage($imageSrc, ['width' => 50]); $section->addObject($objectSrc); $section->addTOC(); $textrun = $section->addTextRun(); $textrun->addText('Test text run'); - $table = $section->addTable(array('width' => 50)); + $table = $section->addTable(['width' => 50]); $cell = $table->addRow()->addCell(); $cell = $table->addRow()->addCell(); $cell->addText('Test'); - $cell->addLink('http://test.com/', 'Test link'); + $cell->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord on GitHub'); $cell->addTextBreak(); $cell->addListItem('Test list item'); $cell->addImage($imageSrc); $cell->addObject($objectSrc); $textrun = $cell->addTextRun(); $textrun->addText('Test text run'); + $section->addPageBreak(); $footer = $section->addFooter(); $footer->addPreserveText('{PAGE}'); @@ -90,21 +92,21 @@ public function testWriteContent() $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); - $element = "/office:document-content/office:body/office:text/text:section/text:p"; - $this->assertEquals($expected, $doc->getElement($element, 'content.xml')->nodeValue); + $element = '/office:document-content/office:body/office:text/text:section/text:p[2]'; + self::assertEquals($expected, $doc->getElement($element, 'content.xml')->nodeValue); } /** - * Test no paragraph style + * Test no paragraph style. */ - public function testWriteNoStyle() + public function testWriteNoStyle(): void { $phpWord = new PhpWord(); - $phpWord->addFontStyle('Font', array('size' => 11)); + $phpWord->addFontStyle('Font', ['size' => 11]); $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); - $element = "/office:document-content/office:automatic-styles/style:style"; - $this->assertTrue($doc->elementExists($element, 'content.xml')); + $element = '/office:document-content/office:automatic-styles/style:style'; + self::assertTrue($doc->elementExists($element, 'content.xml')); } } diff --git a/tests/PhpWordTests/Writer/ODText/Part/ManifestTest.php b/tests/PhpWordTests/Writer/ODText/Part/ManifestTest.php new file mode 100644 index 0000000000..bfe5253cda --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Part/ManifestTest.php @@ -0,0 +1,101 @@ +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 new file mode 100644 index 0000000000..b377ff4fcd --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Style/FontTest.php @@ -0,0 +1,287 @@ +elementExists($path, $file)); + $element = $doc->getElement($path, $file); + + self::assertEquals('#000000', $element->getAttribute('fo:color')); + self::assertEquals('false', $element->getAttribute('style:use-window-font-color')); //has to be set to false so that fo:color can take effect + } + + public function testSettingDefaults(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + + $defaultFontColor = '00FF00'; + $phpWord->setDefaultFontColor($defaultFontColor); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $file = 'styles.xml'; + + $path = '/office:document-styles/office:styles/style:default-style/style:text-properties'; + self::assertTrue($doc->elementExists($path, $file)); + $element = $doc->getElement($path, $file); + + self::assertEquals('#' . $defaultFontColor, $element->getAttribute('fo:color')); + } + + /** + * Test colors. + */ + public function testColors(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $section->addText('This is red (800) in rtf/html, default in docx/odt', ['color' => '800']); + $section->addText('This should be cyanish (008787)', ['color' => '008787']); + $section->addText('This should be dark green (FGCOLOR_DARKGREEN)', ['color' => Font::FGCOLOR_DARKGREEN]); + $section->addText('This color is default (unknow)', ['color' => 'unknow']); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + self::assertTrue($doc->elementExists($s2a)); + $s2t = '/office:document-content/office:body/office:text/text:section'; + self::assertTrue($doc->elementExists($s2t)); + + $element = "$s2a/style:style[5]"; + self::assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('#008787', $doc->getElementAttribute($element, 'fo:color')); + $span = "$s2t/text:p[3]/text:span"; + self::assertTrue($doc->elementExists($span)); + self::assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + self::assertEquals('This should be cyanish (008787)', $doc->getElement($span)->nodeValue); + + $element = "$s2a/style:style[7]"; + self::assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('#006400', $doc->getElementAttribute($element, 'fo:color')); + $span = "$s2t/text:p[4]/text:span"; + self::assertTrue($doc->elementExists($span)); + self::assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + self::assertEquals('This should be dark green (FGCOLOR_DARKGREEN)', $doc->getElement($span)->nodeValue); + } + + public static function providerAllNamedColors() + { + return [ + [Font::FGCOLOR_YELLOW, 'FFFF00'], + [Font::FGCOLOR_LIGHTGREEN, '90EE90'], + [Font::FGCOLOR_CYAN, '00FFFF'], + [Font::FGCOLOR_MAGENTA, 'FF00FF'], + [Font::FGCOLOR_BLUE, '0000FF'], + [Font::FGCOLOR_RED, 'FF0000'], + [Font::FGCOLOR_DARKBLUE, '00008B'], + [Font::FGCOLOR_DARKCYAN, '008B8B'], + [Font::FGCOLOR_DARKGREEN, '006400'], + [Font::FGCOLOR_DARKMAGENTA, '8B008B'], + [Font::FGCOLOR_DARKRED, '8B0000'], + [Font::FGCOLOR_DARKYELLOW, '8B8B00'], + [Font::FGCOLOR_DARKGRAY, 'A9A9A9'], + [Font::FGCOLOR_LIGHTGRAY, 'D3D3D3'], + [Font::FGCOLOR_BLACK, '000000'], + ['unknow', 'unknow'], + ['unknown', 'unknown'], + ]; + } + + /** + * @dataProvider providerAllNamedColors + * + * @param string $namedColor + * @param string $rgbColor + */ + public function testAllNamedColors($namedColor, $rgbColor): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $section->addText('This is red (800) in rtf/html, default in docx/odt', ['color' => '800']); + $section->addText('This should be cyanish (008787)', ['color' => '008787']); + $section->addText($namedColor, ['color' => $namedColor]); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + self::assertTrue($doc->elementExists($s2a)); + $s2t = '/office:document-content/office:body/office:text/text:section'; + self::assertTrue($doc->elementExists($s2t)); + + $element = "$s2a/style:style[7]"; + self::assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals("#$rgbColor", $doc->getElementAttribute($element, 'fo:color')); + $span = "$s2t/text:p[4]/text:span"; + self::assertTrue($doc->elementExists($span)); + self::assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + self::assertEquals($namedColor, $doc->getElement($span)->nodeValue); + } + + /** + * Test noproof. + */ + public function testNoProof(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Noproof not specified', ['color' => 'black']); + $section->addText('Noproof is true', ['color' => 'black', 'noproof' => true]); + $section->addText('Noproof is false', ['color' => 'black', 'noproof' => false]); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + self::assertTrue($doc->elementExists($s2a)); + $s2t = '/office:document-content/office:body/office:text/text:section'; + self::assertTrue($doc->elementExists($s2t)); + + $element = "$s2a/style:style[3]"; + self::assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('', $doc->getElementAttribute($element, 'fo:language')); + $span = "$s2t/text:p[2]/text:span"; + self::assertTrue($doc->elementExists($span)); + self::assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + self::assertEquals('Noproof not specified', $doc->getElement($span)->nodeValue); + + $element = "$s2a/style:style[5]"; + self::assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('zxx', $doc->getElementAttribute($element, 'fo:language')); + self::assertEquals('zxx', $doc->getElementAttribute($element, 'style:language-asian')); + self::assertEquals('zxx', $doc->getElementAttribute($element, 'style:language-complex')); + self::assertEquals('none', $doc->getElementAttribute($element, 'fo:country')); + self::assertEquals('none', $doc->getElementAttribute($element, 'style:country-asian')); + self::assertEquals('none', $doc->getElementAttribute($element, 'style:country-complex')); + $span = "$s2t/text:p[3]/text:span"; + self::assertTrue($doc->elementExists($span)); + self::assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + self::assertEquals('Noproof is true', $doc->getElement($span)->nodeValue); + + $element = "$s2a/style:style[7]"; + self::assertTrue($doc->elementExists($element)); + $style = $doc->getElementAttribute($element, 'style:name'); + $element .= '/style:text-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('', $doc->getElementAttribute($element, 'fo:language')); + $span = "$s2t/text:p[4]/text:span"; + self::assertTrue($doc->elementExists($span)); + self::assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); + self::assertEquals('Noproof is false', $doc->getElement($span)->nodeValue); + } + + /** + * Test using object with a name as font style for addText. + */ + public function testNamedStyleAsObject(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $named = $phpWord->addFontStyle('namedobject', ['color' => '008787']); + $section = $phpWord->addSection(); + $section->addText('Let us see what color we wind up with', $named); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2t = '/office:document-content/office:body/office:text/text:section'; + self::assertTrue($doc->elementExists($s2t)); + $element = "$s2t/text:p[2]/text:span"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('namedobject', $doc->getElementAttribute($element, 'text:style-name')); + } + + /** + * Test supplying field font style as array or object or string. + */ + public function testFieldStyles(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $namedstyle = $phpWord->addFontStyle('namedstyle', ['color' => '800000']); + $section = $phpWord->addSection(); + $textrun = $section->addTextRun(); + $fld = $textrun->addField('DATE'); + $fld->setFontStyle('namedstyle'); + $textrun = $section->addTextRun(); + $fld = $textrun->addField('DATE'); + $fld->setFontStyle(['color' => '008000']); + $textrun = $section->addTextRun(); + $fld = $textrun->addField('DATE'); + $font = new Font(); + $font->setColor('000080'); + $fld->setFontStyle($font); + $textrun = $section->addTextRun(); + $fld = $textrun->addField('DATE'); + $fld->setFontStyle($namedstyle); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $s2t = '/office:document-content/office:body/office:text/text:section'; + + $element = "$s2a/style:style[5]"; + self::assertEquals('T1', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('#008000', $doc->getElementAttribute("$element/style:text-properties", 'fo:color')); + $element = "$s2a/style:style[7]"; + self::assertEquals('T2', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('#000080', $doc->getElementAttribute("$element/style:text-properties", 'fo:color')); + + $element = "$s2t/text:p[2]/text:span"; + self::assertEquals('namedstyle', $doc->getElementAttribute($element, 'text:style-name')); + self::assertTrue($doc->elementExists("$element/text:date")); + $element = "$s2t/text:p[3]/text:span"; + self::assertEquals('T1', $doc->getElementAttribute($element, 'text:style-name')); + self::assertTrue($doc->elementExists("$element/text:date")); + $element = "$s2t/text:p[4]/text:span"; + self::assertEquals('T2', $doc->getElementAttribute($element, 'text:style-name')); + self::assertTrue($doc->elementExists("$element/text:date")); + $element = "$s2t/text:p[5]/text:span"; + self::assertEquals('namedstyle', $doc->getElementAttribute($element, 'text:style-name')); + } +} diff --git a/tests/PhpWordTests/Writer/ODText/Style/NumberingTest.php b/tests/PhpWordTests/Writer/ODText/Style/NumberingTest.php new file mode 100644 index 0000000000..b06242706d --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Style/NumberingTest.php @@ -0,0 +1,70 @@ +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..bc02ca8ea0 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php @@ -0,0 +1,155 @@ + '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/ODText/Style/ParagraphTest.php b/tests/PhpWordTests/Writer/ODText/Style/ParagraphTest.php new file mode 100644 index 0000000000..ecadd387d5 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Style/ParagraphTest.php @@ -0,0 +1,466 @@ +addSection(); + $section->addText('Text on first page'); + $section->addPageBreak(); + $section->addText('Text on second page'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[1]"; + self::assertEquals('PB', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('page', $doc->getElementAttribute($element, 'fo:break-after')); + self::assertEquals('0cm', $doc->getElementAttribute($element, 'fo:margin-top')); + self::assertEquals('0cm', $doc->getElementAttribute($element, 'fo:margin-bottom')); + + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[3]"; + self::assertEquals('PB', $doc->getElementAttribute($element, 'text:style-name')); + } + + /** + * Test normal/indent. + */ + public function testNormalIndent(): void + { + $phpWord = new PhpWord(); + $cvt = Converter::INCH_TO_TWIP; + $indent1 = ['indentation' => ['left' => 0.50 * $cvt]]; + $indent2 = ['indentation' => ['left' => 1.00 * $cvt, 'right' => 1.05 * $cvt]]; + $indent3 = ['indentation' => ['left' => -0.50 * $cvt]]; + $indent4 = ['indentation' => ['left' => 0 * $cvt]]; + $phpWord->setDefaultParagraphStyle($indent1); + $section = $phpWord->addSection(); + $section->addText('Should use default indent (0.5)'); + $section->addText('Should use non-default indent (1.0) on both sides, and here\'s an extra long line to prove it', null, $indent2); + $section->addText('Should use non-default indent (-0.5)', null, $indent3); + $section->addText('Should use non-default indent (0)', null, $indent4); + + $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('', $doc->getElementAttribute($element, 'fo:margin-left')); + self::assertEquals('', $doc->getElementAttribute($element, 'fo:margin-right')); + + $element = "$s2a/style:style[6]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('1in', $doc->getElementAttribute($element, 'fo:margin-left')); + self::assertEquals('1.05in', $doc->getElementAttribute($element, 'fo:margin-right')); + + $element = "$s2a/style:style[8]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('-0.5in', $doc->getElementAttribute($element, 'fo:margin-left')); + self::assertEquals('0in', $doc->getElementAttribute($element, 'fo:margin-right')); + + $element = "$s2a/style:style[10]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('0in', $doc->getElementAttribute($element, 'fo:margin-left')); + self::assertEquals('0in', $doc->getElementAttribute($element, 'fo:margin-right')); + + $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('0.5in', $doc->getElementAttribute($element, 'fo:margin-left')); + self::assertEquals('0in', $doc->getElementAttribute($element, 'fo:margin-right')); + } + + /** + * Test textAlign. + */ + public function testTextAlign(): void + { + $phpWord = new PhpWord(); + $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 left alignment', null, $align2); + $section->addText('Explicit right 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('', $doc->getElementAttribute($element, 'fo:text-align')); + + $element = "$s2a/style:style[6]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('start', $doc->getElementAttribute($element, 'fo:text-align')); + + $element = "$s2a/style:style[8]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('end', $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('end', $doc->getElementAttribute($element, 'fo:text-align')); + } + + /** + * Test lineHeight. + */ + public function testLineHeight(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Should use line height 1.08, and here\'s a long line which ought to overflow onto a second line to prove it', null, ['lineHeight' => 1.08]); + $section->addText('Should use line height 1.20, and here\'s a long line which ought to overflow onto a second line to prove it', null, ['lineHeight' => 1.20]); + $section->addText('Should use line height 0.90, and here\'s a long line which ought to overflow onto a second line to prove it', null, ['lineHeight' => 0.90]); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + self::assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:style[4]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('108%', $doc->getElementAttribute($element, 'fo:line-height')); + + $element = "$s2a/style:style[6]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('120%', $doc->getElementAttribute($element, 'fo:line-height')); + + $element = "$s2a/style:style[8]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('90%', $doc->getElementAttribute($element, 'fo:line-height')); + } + + /** + * Test SpaceBeforeAfter. + */ + public function testSpaceBeforeAfter(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultParagraphStyle(['spaceBefore' => 0, 'spaceAfter' => 0]); + $section = $phpWord->addSection(); + $section->addText('No spacing between this paragraph and next'); + $section->addText('No spacing between this paragraph and previous'); + $section->addText('No spacing before this but 100 after', null, ['spaceAfter' => 100]); + $section->addText('No spacing for this paragraph but previous specified 100 after and next specifies 100 before'); + $section->addText('No spacing after this but 100 before', null, ['spaceBefore' => 100]); + $section->addText('No spacing before this paragraph'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + self::assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:style[8]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('', $doc->getElementAttribute($element, 'fo:margin-top')); + self::assertEquals('5pt', $doc->getElementAttribute($element, 'fo:margin-bottom')); + + $element = "$s2a/style:style[12]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('5pt', $doc->getElementAttribute($element, 'fo:margin-top')); + self::assertEquals('', $doc->getElementAttribute($element, 'fo:margin-bottom')); + + $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('0pt', $doc->getElementAttribute($element, 'fo:margin-top')); + self::assertEquals('0pt', $doc->getElementAttribute($element, 'fo:margin-bottom')); + } + + /** + * Test Page Break Before. + */ + public function testPageBreakBefore(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('This is my first paragraph.'); + $section->addText('This is my second paragraph, on a new page.', null, ['pageBreakBefore' => true]); + $section->addText('This is my third paragraph, on same page as second.'); + $section->addText('This is my fourth paragraph, on a new page.', null, ['pageBreakBefore' => true]); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + self::assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:style[4]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('', $doc->getElementAttribute($element, 'fo:break-before')); + $element = "$s2a/style:style[6]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('page', $doc->getElementAttribute($element, 'fo:break-before')); + $element = "$s2a/style:style[8]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('', $doc->getElementAttribute($element, 'fo:break-before')); + $element = "$s2a/style:style[10]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('page', $doc->getElementAttribute($element, 'fo:break-before')); + } + + /** + * Test Heading Page Break Before. + */ + public function testHeadingPageBreakBefore(): void + { + $phpWord = new PhpWord(); + $phpWord->addTitleStyle(1, null, ['pageBreakBefore' => true]); + $phpWord->addTitleStyle(2, null, []); + $section = $phpWord->addSection(); + $section->addTitle('Section1 Heading1 #1', 1); + $section->addTitle('Section1 Heading2 #1', 2); + $section->addTitle('Section1 Heading1 #2', 1); + $section->addTitle('Section1 Heading2 #2', 2); + $section = $phpWord->addSection(); + $section->addTitle('Section2 Heading1 #1', 1); + $section->addTitle('Section2 Heading2 #1', 2); + + $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('HD1', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Heading_1', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('page', $doc->getElementAttribute($element, 'fo:break-before')); + + $element = "$s2a/style:style[5]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('HE1', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Heading_1', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('auto', $doc->getElementAttribute($element, 'fo:break-before')); + + $element = "$s2a/style:style[6]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('HD2', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Heading_2', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('', $doc->getElementAttribute($element, 'fo:break-before')); + + $element = "$s2a/style:style[7]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('HE2', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Heading_2', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('auto', $doc->getElementAttribute($element, 'fo:break-before')); + + $s2a = '/office:document-content/office:body/office:text/text:section[1]'; + self::assertTrue($doc->elementExists($s2a)); + $element = "$s2a/text:h[1]"; + self::assertEquals('HE1', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + self::assertEquals('Heading_1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:h[2]"; + self::assertEquals('HD2', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('2', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + self::assertEquals('Heading_2', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:h[3]"; + self::assertEquals('HD1', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + self::assertEquals('Heading_1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:h[4]"; + self::assertEquals('HD2', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('2', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + self::assertEquals('Heading_2', $doc->getElementAttribute($element, 'text:style-name')); + + $s2a = '/office:document-content/office:body/office:text/text:section[2]'; + self::assertTrue($doc->elementExists($s2a)); + $element = "$s2a/text:h[1]"; + self::assertEquals('HE1', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('1', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + self::assertEquals('Heading_1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:h[2]"; + self::assertEquals('HD2', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('2', $doc->getElementAttribute($element, 'text:outline-level')); + $element .= '/text:span'; + self::assertEquals('Heading_2', $doc->getElementAttribute($element, 'text:style-name')); + + $doc->setDefaultFile('styles.xml'); + $s2a = '/office:document-styles/office:styles'; + self::assertTrue($doc->elementExists($s2a)); + $element = "$s2a/style:style[1]"; + self::assertEquals('Heading_1', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('paragraph', $doc->getElementAttribute($element, 'style:family')); + $element .= '/style:paragraph-properties'; + self::assertEquals('page', $doc->getElementAttribute($element, 'fo:break-before')); + $element = "$s2a/style:style[3]"; + self::assertEquals('Heading_2', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('paragraph', $doc->getElementAttribute($element, 'style:family')); + $element .= '/style:paragraph-properties'; + self::assertEquals('', $doc->getElementAttribute($element, 'fo:break-before')); + } + + /** + * Test text run paragraph style using named style. + */ + public function testTextRun(): void + { + $phpWord = new PhpWord(); + $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('start', $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('end', $doc->getElementAttribute($element, 'fo:text-align')); + } + + /** + * Test text run paragraph style using unnamed style. + */ + public function testTextRunUnnamed(): void + { + $phpWord = new PhpWord(); + $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('start', $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('end', $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')); + } + + /** + * Test Empty font and paragraph styles. + */ + public function testEmptyFontAndParagraphStyles(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $phpWord->addFontStyle('namedfont', ['name' => 'Courier New', 'size' => 8]); + $phpWord->addParagraphStyle('namedpar', ['lineHeight' => 1.08]); + $section->addText('Empty Font Style and Empty Paragraph Style', '', ''); + $section->addText('Named Font Style and Empty Paragraph Style', 'namedfont', ''); + $section->addText('Empty Font Style and Named Paragraph Style', '', 'namedpar'); + $section->addText('Named Font Style and Named Paragraph Style', 'namedfont', 'namedpar'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[2]"; + self::assertEquals('Normal', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals(5, $doc->getElementAttribute("$element/text:s", 'text:c')); + self::assertFalse($doc->elementExists("$element/text:span")); + $element = "$s2a/text:p[3]"; + self::assertEquals('Normal', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('namedfont', $doc->getElementAttribute("$element/text:span", 'text:style-name')); + $element = "$s2a/text:p[4]"; + self::assertEquals('P1_namedpar', $doc->getElementAttribute($element, 'text:style-name')); + self::assertFalse($doc->elementExists("$element/text:span")); + $element = "$s2a/text:p[5]"; + self::assertEquals('P2_namedpar', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('namedfont', $doc->getElementAttribute("$element/text:span", 'text:style-name')); + } +} diff --git a/tests/PhpWordTests/Writer/ODText/Style/SectionTest.php b/tests/PhpWordTests/Writer/ODText/Style/SectionTest.php new file mode 100644 index 0000000000..83f5962944 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Style/SectionTest.php @@ -0,0 +1,250 @@ +addFontStyle('hdrstyle1', ['name' => 'Courier New', 'size' => 8]); + $section = $phpWord->addSection(['paperSize' => 'Letter', 'marginTop' => $margins, 'marginBottom' => $margins]); + $header = $section->addHeader(); + $phpWord->addParagraphStyle('centerheader', ['align' => 'center']); + $header->addText('Centered Header', 'hdrstyle1', 'centerheader'); + $footer = $section->addFooter(); + $sizew = $section->getStyle()->getPageSizeW(); + $sizel = $section->getStyle()->getMarginLeft(); + $sizer = $section->getStyle()->getMarginRight(); + $footerwidth = $sizew - $sizel - $sizer; + $phpWord->addParagraphStyle( + 'footerTab', + [ + 'tabs' => [ + new \PhpOffice\PhpWord\Style\Tab('center', (int) ($footerwidth / 2)), + new \PhpOffice\PhpWord\Style\Tab('right', (int) $footerwidth), + ], + ] + ); + $textrun = $footer->addTextRun('footerTab'); + $textrun->addText('Left footer', 'hdrstyle1'); + $textrun->addText("\t", 'hdrstyle1'); + $fld = $textrun->addField('DATE'); + $fld->setFontStyle('hdrstyle1'); + $textrun->addText("\t", 'hdrstyle1'); + $textrun->addText('Page ', 'hdrstyle1'); + $fld = $textrun->addField('PAGE'); + $fld->setFontStyle('hdrstyle1'); + $textrun->addText(' of ', 'hdrstyle1'); + $fld = $textrun->addField('NUMPAGES'); + $fld->setFontStyle('hdrstyle1'); + $section->addText('First page'); + $section->addPageBreak(); + $section->addText('Second page'); + $section->addPageBreak(); + $section->addText('Third page'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $doc->setDefaultFile('styles.xml'); + $s2a = '/office:document-styles/office:automatic-styles'; + $element = "$s2a/style:page-layout/style:page-layout-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('8.5in', $doc->getElementAttribute($element, 'fo:page-width')); + self::assertEquals('11in', $doc->getElementAttribute($element, 'fo:page-height')); + self::assertEquals('0.5in', $doc->getElementAttribute($element, 'fo:margin-top')); + self::assertEquals('0.5in', $doc->getElementAttribute($element, 'fo:margin-bottom')); + + $s2s = '/office:document-styles/office:styles'; + $element = "$s2s/style:style[1]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('hdrstyle1', $doc->getElementAttribute($element, 'style:name')); + $tprop = "$element/style:text-properties"; + self::assertTrue($doc->elementExists($tprop)); + self::assertEquals('Courier New', $doc->getElementAttribute($tprop, 'style:font-name')); + + $element = "$s2s/style:style[2]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('centerheader', $doc->getElementAttribute($element, 'style:name')); + $tprop = "$element/style:paragraph-properties"; + self::assertTrue($doc->elementExists($tprop)); + self::assertEquals('center', $doc->getElementAttribute($tprop, 'fo:text-align')); + + $element = "$s2s/style:style[3]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('footerTab', $doc->getElementAttribute($element, 'style:name')); + $tprop = "$element/style:paragraph-properties/style:tab-stops"; + self::assertTrue($doc->elementExists($tprop)); + $tstop = "$tprop/style:tab-stop[1]"; + self::assertTrue($doc->elementExists($tstop)); + self::assertEquals('center', $doc->getElementAttribute($tstop, 'style:type')); + self::assertEquals('3.25in', $doc->getElementAttribute($tstop, 'style:position')); + $tstop = "$tprop/style:tab-stop[2]"; + self::assertTrue($doc->elementExists($tstop)); + self::assertEquals('right', $doc->getElementAttribute($tstop, 'style:type')); + self::assertEquals('6.5in', $doc->getElementAttribute($tstop, 'style:position')); + + $s2s = '/office:document-styles/office:master-styles/style:master-page/style:footer/text:p'; + self::assertTrue($doc->elementExists($s2s)); + $element = "$s2s/text:span[1]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('hdrstyle1', $doc->getElementAttribute($element, 'text:style-name')); + self::assertEquals('Left footer', $doc->getElement($element)->nodeValue); + $element = "$s2s/text:span[2]/text:tab"; + self::assertTrue($doc->elementExists($element)); + $element = "$s2s/text:span[3]/text:date"; + self::assertTrue($doc->elementExists($element)); + $element = "$s2s/text:span[4]/text:tab"; + self::assertTrue($doc->elementExists($element)); + $element = "$s2s/text:span[5]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('Page', $doc->getElement($element)->nodeValue); + self::assertTrue($doc->elementExists("$element/text:s")); + $element = "$s2s/text:span[6]/text:page-number"; + self::assertTrue($doc->elementExists($element)); + $element = "$s2s/text:span[7]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('of', $doc->getElement($element)->nodeValue); + self::assertTrue($doc->elementExists("$element/text:s")); + self::assertTrue($doc->elementExists("$element/text:s[2]")); + $element = "$s2s/text:span[8]/text:page-count"; + self::assertTrue($doc->elementExists($element)); + } + + /** + * Test HideErrors. + */ + public function testHideErrors(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setHideGrammaticalErrors(true); + $phpWord->getSettings()->setHideSpellingErrors(true); + $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('en-US')); + $phpWord->getSettings()->getThemeFontLang()->setLangId(\PhpOffice\PhpWord\Style\Language::EN_US_ID); + $section = $phpWord->addSection(); + $section->addText('Here is a paragraph with some speling errorz'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:default-style/style:text-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('zxx', $doc->getElementAttribute($element, 'fo:language')); + self::assertEquals('zxx', $doc->getElementAttribute($element, 'style:language-asian')); + self::assertEquals('zxx', $doc->getElementAttribute($element, 'style:language-complex')); + self::assertEquals('none', $doc->getElementAttribute($element, 'fo:country')); + self::assertEquals('none', $doc->getElementAttribute($element, 'style:country-asian')); + self::assertEquals('none', $doc->getElementAttribute($element, 'style:country-complex')); + } + + /** + * Test SpaceBeforeAfter. + */ + public function testMultipleSections(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(['paperSize' => 'Letter', 'Orientation' => 'portrait']); + $section->addText('This section uses Letter paper in portrait orientation.'); + $section = $phpWord->addSection(['paperSize' => 'A4', 'Orientation' => 'landscape', 'pageNumberingStart' => '9']); + $header = $section->addHeader(); + $header->addField('PAGE'); + $section->addText('This section uses A4 paper in landscape orientation. It should have a page break beforehand. It artificially starts on page 9.'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $s2t = '/office:document-content/office:body/office:text'; + self::assertTrue($doc->elementExists($s2a)); + self::assertTrue($doc->elementExists($s2t)); + + $element = "$s2a/style:style[2]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('SB1', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Standard1', $doc->getElementAttribute($element, 'style:master-page-name')); + $element .= '/style:text-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('none', $doc->getElementAttribute($element, 'text:display')); + $element = "$s2a/style:style[3]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('SB2', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Standard2', $doc->getElementAttribute($element, 'style:master-page-name')); + $elemen2 = "$element/style:paragraph-properties"; + self::assertEquals('9', $doc->getElementAttribute($elemen2, 'style:page-number')); + $element .= '/style:text-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('none', $doc->getElementAttribute($element, 'text:display')); + + $element = "$s2t/text:section[1]"; + self::assertTrue($doc->elementExists($element)); + $element .= '/text:p[1]'; + self::assertEquals('SB1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2t/text:section[2]"; + self::assertTrue($doc->elementExists($element)); + $element .= '/text:p[1]'; + self::assertEquals('SB2', $doc->getElementAttribute($element, 'text:style-name')); + + $doc->setDefaultFile('styles.xml'); + $s2a = '/office:document-styles/office:automatic-styles'; + self::assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:page-layout[1]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('Mpm1', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:page-layout-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('8.5in', $doc->getElementAttribute($element, 'fo:page-width')); + self::assertEquals('11in', $doc->getElementAttribute($element, 'fo:page-height')); + self::assertEquals('portrait', $doc->getElementAttribute($element, 'style:print-orientation')); + + $element = "$s2a/style:page-layout[2]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('Mpm2', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:page-layout-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('29.7cm', $doc->getElementAttribute($element, 'fo:page-width')); + self::assertEquals('21cm', $doc->getElementAttribute($element, 'fo:page-height')); + self::assertEquals('landscape', $doc->getElementAttribute($element, 'style:print-orientation')); + + $s2a = '/office:document-styles/office:master-styles'; + self::assertTrue($doc->elementExists($s2a)); + $element = "$s2a/style:master-page[1]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('Standard1', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Mpm1', $doc->getElementAttribute($element, 'style:page-layout-name')); + $element = "$s2a/style:master-page[2]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('Standard2', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Mpm2', $doc->getElementAttribute($element, 'style:page-layout-name')); + } +} diff --git a/tests/PhpWord/Tests/Writer/ODText/StyleTest.php b/tests/PhpWordTests/Writer/ODText/StyleTest.php similarity index 70% rename from tests/PhpWord/Tests/Writer/ODText/StyleTest.php rename to tests/PhpWordTests/Writer/ODText/StyleTest.php index cd5ea0eb15..21d4c8bb53 100644 --- a/tests/PhpWord/Tests/Writer/ODText/StyleTest.php +++ b/tests/PhpWordTests/Writer/ODText/StyleTest.php @@ -1,4 +1,5 @@ write(); - $this->assertEquals('', $xmlWriter->getData()); + self::assertEquals('', $xmlWriter->getData()); } } } diff --git a/tests/PhpWord/Tests/Writer/ODTextTest.php b/tests/PhpWordTests/Writer/ODTextTest.php similarity index 53% rename from tests/PhpWord/Tests/Writer/ODTextTest.php rename to tests/PhpWordTests/Writer/ODTextTest.php index 25b8095b10..9746791379 100644 --- a/tests/PhpWord/Tests/Writer/ODTextTest.php +++ b/tests/PhpWordTests/Writer/ODTextTest.php @@ -1,4 +1,5 @@ assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $object->getPhpWord()); + self::assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $object->getPhpWord()); - $this->assertEquals('./', $object->getDiskCachingDirectory()); - foreach (array('Content', 'Manifest', 'Meta', 'Mimetype', 'Styles') as $part) { - $this->assertInstanceOf( + self::assertEquals('./', $object->getDiskCachingDirectory()); + foreach (['Content', 'Manifest', 'Meta', 'Mimetype', 'Styles'] as $part) { + self::assertInstanceOf( "PhpOffice\\PhpWord\\Writer\\ODText\\Part\\{$part}", $object->getWriterPart($part) ); - $this->assertInstanceOf( + self::assertInstanceOf( 'PhpOffice\\PhpWord\\Writer\\ODText', $object->getWriterPart($part)->getParentWriter() ); @@ -49,34 +52,33 @@ public function testConstruct() } /** - * Construct with null - * - * @expectedException \PhpOffice\PhpWord\Exception\Exception - * @expectedExceptionMessage No PhpWord assigned. + * Construct with null. */ - public function testConstructWithNull() + public function testConstructWithNull(): void { + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectExceptionMessage('No PhpWord assigned.'); $object = new ODText(); $object->getPhpWord(); } /** - * Save + * Save. */ - public function testSave() + public function testSave(): void { - $imageSrc = __DIR__ . "/../_files/images/PhpWord.png"; - $objectSrc = __DIR__ . "/../_files/documents/sheet.xls"; - $file = __DIR__ . "/../_files/temp.odt"; + $imageSrc = __DIR__ . '/../_files/images/PhpWord.png'; + $objectSrc = __DIR__ . '/../_files/documents/sheet.xls'; + $file = __DIR__ . '/../_files/temp.odt'; $phpWord = new PhpWord(); - $phpWord->addFontStyle('Font', array('size' => 11)); - $phpWord->addParagraphStyle('Paragraph', array('align' => 'center')); + $phpWord->addFontStyle('Font', ['size' => 11]); + $phpWord->addParagraphStyle('Paragraph', ['alignment' => Jc::CENTER]); $section = $phpWord->addSection(); $section->addText('Test 1', 'Font'); $section->addTextBreak(); $section->addText('Test 2', null, 'Paragraph'); - $section->addLink('http://test.com/'); + $section->addLink('https://github.com/PHPOffice/PHPWord'); $section->addTitle('Test', 1); $section->addPageBreak(); $section->addTable()->addRow()->addCell()->addText('Test'); @@ -90,56 +92,56 @@ public function testSave() $writer = new ODText($phpWord); $writer->save($file); - $this->assertTrue(file_exists($file)); + self::assertFileExists($file); unlink($file); } /** - * Save php output + * Save php output. * * @todo Haven't got any method to test this */ - public function testSavePhpOutput() + public function testSavePhpOutput(): void { $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Test'); $writer = new ODText($phpWord); + ob_start(); $writer->save('php://output'); + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); + self::assertNotEmpty($contents); } /** - * Get writer part return null value + * Get writer part return null value. */ - public function testGetWriterPartNull() + public function testGetWriterPartNull(): void { $object = new ODText(); - $this->assertNull($object->getWriterPart('foo')); + self::assertNull($object->getWriterPart('foo')); } /** - * Set/get use disk caching + * Set/get use disk caching. */ - public function testSetGetUseDiskCaching() + public function testSetGetUseDiskCaching(): void { $object = new ODText(); $object->setUseDiskCaching(true, PHPWORD_TESTS_BASE_DIR); - $this->assertTrue($object->isUseDiskCaching()); - $this->assertEquals(PHPWORD_TESTS_BASE_DIR, $object->getDiskCachingDirectory()); + self::assertTrue($object->isUseDiskCaching()); + self::assertEquals(PHPWORD_TESTS_BASE_DIR, $object->getDiskCachingDirectory()); } /** - * Use disk caching exception - * - * @expectedException \PhpOffice\PhpWord\Exception\Exception + * Use disk caching exception. */ - public function testSetUseDiskCachingException() + public function testSetUseDiskCachingException(): void { - $dir = join( - DIRECTORY_SEPARATOR, - array(PHPWORD_TESTS_BASE_DIR, 'foo') - ); + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $dir = implode(DIRECTORY_SEPARATOR, [PHPWORD_TESTS_BASE_DIR, 'foo']); $object = new ODText(); $object->setUseDiskCaching(true, $dir); diff --git a/tests/PhpWord/Tests/Writer/PDF/DomPDFTest.php b/tests/PhpWordTests/Writer/PDF/DomPDFTest.php similarity index 56% rename from tests/PhpWord/Tests/Writer/PDF/DomPDFTest.php rename to tests/PhpWordTests/Writer/PDF/DomPDFTest.php index 476c9011dc..37ea762802 100644 --- a/tests/PhpWord/Tests/Writer/PDF/DomPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/DomPDFTest.php @@ -1,4 +1,5 @@ addSection(); @@ -45,15 +47,15 @@ public function testConstruct() $writer = new PDF($phpWord); $writer->save($file); - $this->assertTrue(file_exists($file)); + self::assertFileExists($file); unlink($file); } /** - * Test set/get abstract renderer properties + * Test set/get abstract renderer properties. */ - public function testSetGetAbstractRendererProperties() + public function testSetGetAbstractRendererProperties(): void { define('DOMPDF_ENABLE_AUTOLOAD', false); @@ -63,15 +65,32 @@ public function testSetGetAbstractRendererProperties() $writer = new PDF(new PhpWord()); $writer->setFont('arial'); - $this->assertEquals('arial', $writer->getFont()); + self::assertEquals('arial', $writer->getFont()); $writer->setPaperSize(); - $this->assertEquals(9, $writer->getPaperSize()); + self::assertEquals(9, $writer->getPaperSize()); $writer->setOrientation(); - $this->assertEquals('default', $writer->getOrientation()); + self::assertEquals('default', $writer->getOrientation()); $writer->setTempDir(Settings::getTempDir()); - $this->assertEquals(Settings::getTempDir(), $writer->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 new file mode 100644 index 0000000000..dc779bd51f --- /dev/null +++ b/tests/PhpWordTests/Writer/PDF/MPDFTest.php @@ -0,0 +1,116 @@ +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); + $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 = '
    {PAGENO}
    ' . MPDF::SIMULATED_BODY_START; + $beforeBody = ''; + $needle = ''; + $pos = strpos($html, $needle); + if ($pos !== false) { + $html = (string) substr_replace($html, "$beforeBody\n$needle", $pos, strlen($needle)); + } + $needle = ''; + $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/PhpWord/Tests/Writer/PDF/TCPDFTest.php b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php similarity index 52% rename from tests/PhpWord/Tests/Writer/PDF/TCPDFTest.php rename to tests/PhpWordTests/Writer/PDF/TCPDFTest.php index c73f004352..16887507a9 100644 --- a/tests/PhpWord/Tests/Writer/PDF/TCPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php @@ -1,4 +1,5 @@ setDefaultParagraphStyle(['spaceBefore' => 0, 'spaceAfter' => 0]); $section = $phpWord->addSection(); $section->addText('Test 1'); $rendererName = Settings::PDF_RENDERER_TCPDF; - $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnick.com/tcpdf'); + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); $writer = new PDF($phpWord); $writer->save($file); - $this->assertTrue(file_exists($file)); + self::assertFileExists($file); 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/PhpWord/Tests/Writer/PDFTest.php b/tests/PhpWordTests/Writer/PDFTest.php similarity index 64% rename from tests/PhpWord/Tests/Writer/PDFTest.php rename to tests/PhpWordTests/Writer/PDFTest.php index 87814b411f..b9e31511cf 100644 --- a/tests/PhpWord/Tests/Writer/PDFTest.php +++ b/tests/PhpWordTests/Writer/PDFTest.php @@ -1,4 +1,5 @@ save($file); - $this->assertTrue(file_exists($file)); + self::assertFileExists($file); unlink($file); } /** - * Test construct exception - * - * @expectedException \PhpOffice\PhpWord\Exception\Exception - * @expectedExceptionMessage PDF rendering library or library path has not been defined. + * Test construct exception. */ - public function testConstructException() + 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..7c1ceac68e --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php @@ -0,0 +1,170 @@ +write()); + } + + public function testTable(): void + { + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new 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..2220d93b68 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element2Test.php @@ -0,0 +1,79 @@ +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 new file mode 100644 index 0000000000..36504a18f8 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/ElementTest.php @@ -0,0 +1,219 @@ +write()); + } + + /** + * Test unmatched elements. + */ + public function testUnmatchedElements(): void + { + $elements = ['Container', 'Text', 'Title', 'Link', 'Image', 'Table', 'Field']; + foreach ($elements as $element) { + $objectClass = 'PhpOffice\\PhpWord\\Writer\\RTF\\Element\\' . $element; + $parentWriter = new RTF(); + $newElement = new \PhpOffice\PhpWord\Element\PageBreak(); + $object = new $objectClass($parentWriter, $newElement); + + self::assertEquals('', $object->write()); + } + } + + public function testFilenameField(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Field('FILENAME'); + $field = new 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 RTF\Element\Field($parentWriter, $element); + + self::assertEquals("{\\field{\\*\\fldinst FILENAME \\\\p}{\\fldrslt}}\\par\n", $this->removeCr($field)); + } + + public function testPageField(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Field('PAGE'); + $field = new RTF\Element\Field($parentWriter, $element); + + self::assertEquals("{\\field{\\*\\fldinst PAGE}{\\fldrslt}}\\par\n", $this->removeCr($field)); + } + + public function testNumpageField(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Field('NUMPAGES'); + $field = new RTF\Element\Field($parentWriter, $element); + + self::assertEquals("{\\field{\\*\\fldinst NUMPAGES}{\\fldrslt}}\\par\n", $this->removeCr($field)); + } + + public function testDateField(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Field('DATE', ['dateformat' => 'd MM yyyy H:mm:ss']); + $field = new RTF\Element\Field($parentWriter, $element); + + self::assertEquals("{\\field{\\*\\fldinst DATE \\\\@ \"d MM yyyy H:mm:ss\"}{\\fldrslt}}\\par\n", $this->removeCr($field)); + } + + public function testIndexField(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Field('INDEX'); + $field = new RTF\Element\Field($parentWriter, $element); + + self::assertEquals("{}\\par\n", $this->removeCr($field)); + } + + public function testTable(): void + { + $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 RTF\Element\Table($parentWriter, $element); + $expect = implode("\n", [ + '\\pard', + "\\trowd \\cellx$width \\cellx$width2 ", + '\\intbl', + '{\\cf0\\f0 1}\\par', + '\\cell', + '\\intbl', + '{\\cf0\\f0 2}\\par', + '\\cell', + '\\row', + "\\trowd \\cellx$width \\cellx$width2 ", + '\\intbl', + '{\\cf0\\f0 3}\\par', + '\\cell', + '\\intbl', + '{\\cf0\\f0 4}\par', + '\\cell', + '\\row', + '\\pard', + '', + ]); + + self::assertEquals($expect, $this->removeCr($table)); + } + + public function testTextRun(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\TextRun(); + $element->addText('Hello '); + $element->addText('there.'); + $textrun = new RTF\Element\TextRun($parentWriter, $element); + $expect = "\\pard\\nowidctlpar {{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + self::assertEquals($expect, $this->removeCr($textrun)); + } + + public function testTextRunParagraphStyle(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\TextRun(['spaceBefore' => 0, 'spaceAfter' => 0]); + $element->addText('Hello '); + $element->addText('there.'); + $textrun = new RTF\Element\TextRun($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\sb0\\sa0{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + self::assertEquals($expect, $this->removeCr($textrun)); + } + + public function testTitle(): void + { + $parentWriter = new RTF(); + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $phpWord->addTitleStyle(1, [], ['spaceBefore' => 0, 'spaceAfter' => 0]); + $section = $phpWord->addSection(); + $element = $section->addTitle('First Heading', 1); + $elwrite = new RTF\Element\Title($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\sb0\\sa0{\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}"; + self::assertEquals($expect, $this->removeCr($elwrite)); + } + + public function testRuby(): void + { + $parentWriter = new RTF(); + $properties = new \PhpOffice\PhpWord\ComplexType\RubyProperties(); + $baseTextRun = new \PhpOffice\PhpWord\Element\TextRun(null); + $baseTextRun->addText('base text'); + $rubyTextRun = new \PhpOffice\PhpWord\Element\TextRun(null); + $rubyTextRun->addText('ruby'); + $element = new \PhpOffice\PhpWord\Element\TextRun(); + $element->addRuby($baseTextRun, $rubyTextRun, $properties); + + $textrun = new RTF\Element\TextRun($parentWriter, $element); + $expect = "\\pard\\nowidctlpar {{base text (ruby)}}\\par\n"; + self::assertEquals($expect, $this->removeCr($textrun)); + } + + public function testRubyTitle(): void + { + $parentWriter = new RTF(); + $properties = new \PhpOffice\PhpWord\ComplexType\RubyProperties(); + $baseTextRun = new \PhpOffice\PhpWord\Element\TextRun(null); + $baseTextRun->addText('base text'); + $rubyTextRun = new \PhpOffice\PhpWord\Element\TextRun(null); + $rubyTextRun->addText('ruby'); + $textRun = new \PhpOffice\PhpWord\Element\TextRun(); + $textRun->addRuby($baseTextRun, $rubyTextRun, $properties); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $phpWord->addTitleStyle( + 1, + ['size' => 24, 'bold' => true, 'color' => '000099'], + ['spaceBefore' => 0, 'spaceAfter' => 2] + ); + $section = $phpWord->addSection(); + $element = $section->addTitle($textRun, 1); + $elwrite = new RTF\Element\Title($parentWriter, $element); + + $expect = "\\pard\\nowidctlpar \\sb0\\sa2{\\outlinelevel0{\\cf0\\f0\\fs48\\b base text (ruby)}\\par\n}"; + self::assertEquals($expect, $this->removeCr($elwrite)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/HeaderFooterTest.php b/tests/PhpWordTests/Writer/RTF/HeaderFooterTest.php new file mode 100644 index 0000000000..3b4f86c7d7 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/HeaderFooterTest.php @@ -0,0 +1,79 @@ +addSection(); + $section->addText('Doc without header or footer'); + $contents = $parentWriter->getWriterPart('Document')->write(); + self::assertEquals(0, preg_match('/\\\\header[rlf]?\\b/', $contents)); + self::assertEquals(0, preg_match('/\\\\footer[rlf]?\\b/', $contents)); + self::assertEquals(0, preg_match('/\\\\titlepg\\b/', $contents)); + self::assertEquals(0, preg_match('/\\\\facingp\\b/', $contents)); + } + + public function testNoHeaderYesFooter(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $parentWriter = new RTF($phpWord); + $section = $phpWord->addSection(); + $footer = $section->addFooter(); + $footer->addText('Auto footer'); + $section->addText('Doc without header but with footer'); + $contents = $parentWriter->getWriterPart('Document')->write(); + self::assertEquals(0, preg_match('/\\\\header[rlf]?\\b/', $contents)); + self::assertEquals(1, preg_match('/\\\\footer[rlf]?\\b/', $contents)); + self::assertEquals(0, preg_match('/\\\\titlepg\\b/', $contents)); + self::assertEquals(0, preg_match('/\\\\facingp\\b/', $contents)); + } + + public function testEvenHeaderFirstFooter(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $phpWord->getSettings()->setEvenAndOddHeaders(true); + $parentWriter = new RTF($phpWord); + $section = $phpWord->addSection(); + $footer = $section->addFooter(Footer::FIRST); + $footer->addText('First footer'); + $footer = $section->addHeader(Footer::EVEN); + $footer->addText('Even footer'); + $footer = $section->addHeader(Footer::AUTO); + $footer->addText('Odd footer'); + $section->addText('Doc with even/odd header and first footer'); + $contents = $parentWriter->getWriterPart('Document')->write(); + self::assertEquals(1, preg_match('/\\\\headerr\\b/', $contents)); + self::assertEquals(1, preg_match('/\\\\headerl\\b/', $contents)); + self::assertEquals(0, preg_match('/\\\\header[f]?\\b/', $contents)); + self::assertEquals(1, preg_match('/\\\\footerf\\b/', $contents)); + self::assertEquals(0, preg_match('/\\\\footer[rl]?\\b/', $contents)); + self::assertEquals(1, preg_match('/\\\\titlepg\\b/', $contents)); + self::assertEquals(1, preg_match('/\\\\facingp\\b/', $contents)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/StyleTest.php b/tests/PhpWordTests/Writer/RTF/StyleTest.php new file mode 100644 index 0000000000..8ba2bcb9c9 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/StyleTest.php @@ -0,0 +1,183 @@ +write()); + } + + /** + * Test empty styles. + */ + public function testEmptyStyles(): void + { + $styles = ['Font', 'Paragraph', 'Section', 'Tab', 'Indentation']; + foreach ($styles as $style) { + $objectClass = 'PhpOffice\\PhpWord\\Writer\\RTF\\Style\\' . $style; + $object = new $objectClass(); + + self::assertEquals('', $object->write()); + } + } + + public function testBorderWithNonRegisteredColors(): void + { + $border = new Border(); + $border->setSizes([1, 2, 3, 4]); + $border->setColors(['#FF0000', '#FF0000', '#FF0000', '#FF0000']); + $border->setSizes([20, 20, 20, 20]); + + $content = $border->write(); + + $expected = '\pgbrdropt32'; + $expected .= '\pgbrdrt\brdrs\brdrw20\brdrcf0\brsp480 '; + $expected .= '\pgbrdrl\brdrs\brdrw20\brdrcf0\brsp480 '; + $expected .= '\pgbrdrr\brdrs\brdrw20\brdrcf0\brsp480 '; + $expected .= '\pgbrdrb\brdrs\brdrw20\brdrcf0\brsp480 '; + + self::assertEquals($expected, $content); + } + + public function testIndentation(): void + { + $indentation = new \PhpOffice\PhpWord\Style\Indentation(); + $indentation->setLeft(1); + $indentation->setRight(2); + $indentation->setFirstLine(3); + + $indentWriter = new RTF\Style\Indentation($indentation); + $indentWriter->setParentWriter(new RTF()); + $result = $indentWriter->write(); + + Assert::assertEquals('\fi3\li1\ri2 ', $result); + } + + public function testRightTab(): void + { + $tabRight = new \PhpOffice\PhpWord\Style\Tab(); + $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_RIGHT); + $tabRight->setPosition(5); + + $tabWriter = new RTF\Style\Tab($tabRight); + $tabWriter->setParentWriter(new RTF()); + $result = $tabWriter->write(); + + Assert::assertEquals('\tqr\tx5', $result); + } + + public function testCenterTab(): void + { + $tabRight = new \PhpOffice\PhpWord\Style\Tab(); + $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_CENTER); + + $tabWriter = new RTF\Style\Tab($tabRight); + $tabWriter->setParentWriter(new RTF()); + $result = $tabWriter->write(); + + Assert::assertEquals('\tqc\tx0', $result); + } + + public function testDecimalTab(): void + { + $tabRight = new \PhpOffice\PhpWord\Style\Tab(); + $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_DECIMAL); + + $tabWriter = new RTF\Style\Tab($tabRight); + $tabWriter->setParentWriter(new RTF()); + $result = $tabWriter->write(); + + Assert::assertEquals('\tqdec\tx0', $result); + } + + public function testRTL(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('אב גד', ['RTL' => true]); + $text = new RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar {\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + + public function testRTL2(): void + { + Settings::setDefaultRtl(true); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('אב גד'); + $text = new RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\qr{\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + + public function testPageBreakLineHeight(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); + $text = new RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + + public function testPageBreakLineHeight2(): void + { + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); + $text = new RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\ql\\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + + public function testPageNumberRestart(): void + { + //$parentWriter = new RTF(); + $phpword = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpword->addSection(['pageNumberingStart' => 5]); + $styleWriter = new RTF\Style\Section($section->getStyle()); + $wstyle = $this->removeCr($styleWriter); + // following have default values which might change so don't use them + $wstyle = preg_replace('/\\\\pgwsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\pghsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\margtsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\margrsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\margbsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\marglsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\headery\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\footery\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\guttersxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/ +/', ' ', $wstyle); + $expect = "\\sectd \\pgnstarts5\\pgnrestart \n"; + self::assertEquals($expect, $wstyle); + } +} diff --git a/tests/PhpWordTests/Writer/RTFTest.php b/tests/PhpWordTests/Writer/RTFTest.php new file mode 100644 index 0000000000..4f9f8944ac --- /dev/null +++ b/tests/PhpWordTests/Writer/RTFTest.php @@ -0,0 +1,118 @@ +getPhpWord()); + } + + /** + * Construct with null. + */ + public function testConstructWithNull(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectExceptionMessage('No PhpWord assigned.'); + $object = new RTF(); + $object->getPhpWord(); + } + + /** + * Save. + */ + public function testSave(): void + { + $imageSrc = __DIR__ . '/../_files/images/PhpWord.png'; + $objectSrc = __DIR__ . '/../_files/documents/sheet.xls'; + $file = __DIR__ . '/../_files/temp.rtf'; + + $phpWord = new PhpWord(); + $phpWord->addFontStyle( + 'Font', + ['name' => 'Verdana', 'size' => 11, 'color' => 'FF0000', 'fgColor' => '00FF00'] + ); + $phpWord->addParagraphStyle('Paragraph', ['alignment' => Jc::CENTER]); + $section = $phpWord->addSection(); + $section->addText(htmlspecialchars('Test 1', ENT_COMPAT, 'UTF-8'), 'Font', 'Paragraph'); + $section->addTextBreak(); + $section->addText(htmlspecialchars('Test 2', ENT_COMPAT, 'UTF-8'), ['name' => 'Tahoma', 'bold' => true, 'italic' => true]); + $section->addLink('https://github.com/PHPOffice/PHPWord'); + $section->addTitle(htmlspecialchars('Test', ENT_COMPAT, 'UTF-8'), 1); + $section->addPageBreak(); + + // Rowspan + $table = $section->addTable(); + $table->addRow()->addCell(null, ['vMerge' => 'restart'])->addText('Test'); + $table->addRow()->addCell(null, ['vMerge' => 'continue'])->addText('Test'); + + // Nested table + $cell = $section->addTable()->addRow()->addCell(); + $cell->addTable()->addRow()->addCell(); + + $section->addListItem(htmlspecialchars('Test', ENT_COMPAT, 'UTF-8')); + $section->addImage($imageSrc); + $section->addObject($objectSrc); + $section->addTOC(); + $section = $phpWord->addSection(); + $textrun = $section->addTextRun(); + $textrun->addText(htmlspecialchars('Test 3', ENT_COMPAT, 'UTF-8')); + $textrun->addTextBreak(); + $writer = new RTF($phpWord); + $writer->save($file); + + self::assertFileExists($file); + + @unlink($file); + } + + /** + * Save. + * + * @todo Haven't got any method to test this + */ + public function testSavePhpOutput(): 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'); + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); + self::assertNotEmpty($contents); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php b/tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php new file mode 100644 index 0000000000..4951da39ce --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php @@ -0,0 +1,242 @@ +outputEscapingEnabled = Settings::isOutputEscapingEnabled(); + } + + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + Settings::setOutputEscapingEnabled($this->outputEscapingEnabled); + TestHelperDOCX::clear(); + } + + /** + * Test chart elements. + */ + public function testChartElements(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $style = [ + 'width' => 5000000, + 'height' => 5000000, + 'showAxisLabels' => true, + 'showGridX' => true, + 'showGridY' => true, + 'showLegend' => false, + ]; + + $chartTypes = ['pie', 'doughnut', 'bar', 'line', 'area', 'scatter', 'radar']; + $categories = ['A', 'B', 'C', 'D', 'E']; + $series1 = [1, 3, 2, 5, 4]; + foreach ($chartTypes as $chartType) { + $section->addChart($chartType, $categories, $series1, $style); + } + $colorArray = ['FFFFFF', '000000', 'FF0000', '00FF00', '0000FF']; + $numColor = count($colorArray); + $chart = $section->addChart('pie', $categories, $series1, $style); + $chart->getStyle()->setColors($colorArray)->setTitle('3d chart')->set3d(true); + $chart = $section->addChart('stacked_bar', $categories, $series1, $style); + $chart->getStyle()->setColors($colorArray)->setShowLegend(true); + $chart = $section->addChart('scatter', $categories, $series1, $style); + $chart->getStyle()->setMajorTickPosition('cross'); + $section->addChart('scatter', $categories, $series1, $style, 'seriesname'); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $index = 0; + foreach ($chartTypes as $chartType) { + ++$index; + $file = "word/charts/chart{$index}.xml"; + $path = "/c:chartSpace/c:chart/c:plotArea/c:{$chartType}Chart"; + self::assertTrue($doc->elementExists($path, $file), "chart type $chartType"); + } + + $index = 11; + $file = "word/charts/chart{$index}.xml"; + $doc->setDefaultFile($file); + $chartType = 'scatter'; + $path = "/c:chartSpace/c:chart/c:plotArea/c:{$chartType}Chart"; + self::assertEquals('seriesname', $doc->getElement($path . '/c:ser/c:tx/c:strRef/c:strCache/c:pt/c:v')->nodeValue); + + $index = 8; + $file = "word/charts/chart{$index}.xml"; + $doc->setDefaultFile($file); + $chartType = 'pie3D'; + $path = "/c:chartSpace/c:chart/c:plotArea/c:{$chartType}Chart"; + for ($idx = 0; $idx < $numColor; ++$idx) { + $idxp1 = $idx + 1; + $element = $path . "/c:ser/c:dPt[$idxp1]/c:spPr/a:solidFill/a:srgbClr"; + self::assertEquals($colorArray[$idx], $doc->getElementAttribute($element, 'val'), "pie3d chart idx=$idx"); + } + + $index = 9; + $file = "word/charts/chart{$index}.xml"; + $doc->setDefaultFile($file); + $chartType = 'bar'; + $path = "/c:chartSpace/c:chart/c:plotArea/c:{$chartType}Chart"; + for ($idxp1 = 1; $idxp1 < $numColor; ++$idxp1) { + $idx = $idxp1; // stacked bar chart is shifted + $element = $path . "/c:ser/c:dPt[$idxp1]/c:spPr/a:solidFill/a:srgbClr"; + self::assertEquals($colorArray[$idx - 1], $doc->getElementAttribute($element, 'val'), "bar chart idx=$idx"); + } + } + + public function testChartEscapingEnabled(): void + { + Settings::setOutputEscapingEnabled(true); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $style = [ + 'width' => 5000000, + 'height' => 5000000, + 'showAxisLabels' => true, + 'showGridX' => true, + 'showGridY' => true, + 'showLegend' => false, + 'valueAxisTitle' => 'Values', + ]; + $categories = ['A&B', 'C', 'E', 'F', 'G']; + $series1 = [1, 3, 2, 5, 4]; + $section->addChart('bar', $categories, $series1, $style); + $doc = TestHelperDOCX::getDocument($phpWord); + + $index = 1; + $file = "word/charts/chart{$index}.xml"; + $doc->setDefaultFile($file); + $chartType = 'bar'; + $path = "/c:chartSpace/c:chart/c:plotArea/c:{$chartType}Chart/c:ser/c:cat/c:strLit"; + $element = "$path/c:pt[1]/c:v"; + self::assertEquals('A&B', $doc->getElement($element)->nodeValue); + $element = "$path/c:pt[2]/c:v"; + self::assertEquals('C', $doc->getElement($element)->nodeValue); + } + + public function testChartEscapingDisabled(): void + { + Settings::setOutputEscapingEnabled(false); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $style = [ + 'width' => 5000000, + 'height' => 5000000, + 'showAxisLabels' => true, + 'showGridX' => true, + 'showGridY' => true, + 'showLegend' => false, + 'valueAxisTitle' => 'Values', + ]; + $categories = ['A&B', 'C<D>', 'E', 'F', 'G']; + $series1 = [1, 3, 2, 5, 4]; + $section->addChart('bar', $categories, $series1, $style); + $doc = TestHelperDOCX::getDocument($phpWord); + + $index = 1; + $file = "word/charts/chart{$index}.xml"; + $doc->setDefaultFile($file); + $chartType = 'bar'; + $path = "/c:chartSpace/c:chart/c:plotArea/c:{$chartType}Chart/c:ser/c:cat/c:strLit"; + $element = "$path/c:pt[1]/c:v"; + self::assertEquals('A&B', $doc->getElement($element)->nodeValue); + $element = "$path/c:pt[2]/c:v"; + self::assertEquals('C', $doc->getElement($element)->nodeValue); + } + + public function testValueAxisTitle(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $style = [ + 'width' => 5000000, + 'height' => 5000000, + 'showAxisLabels' => true, + 'showGridX' => true, + 'showGridY' => true, + 'showLegend' => false, + 'valueAxisTitle' => 'Values', + ]; + $chartType = 'line'; + $categories = ['A', 'B', 'C', 'D', 'E']; + $series1 = [1, 3, 2, 5, 4]; + $section->addChart($chartType, $categories, $series1, $style); + $doc = TestHelperDOCX::getDocument($phpWord); + + $index = 1; + $file = "word/charts/chart{$index}.xml"; + $doc->setDefaultFile($file); + $chartType = 'line'; + $path = '/c:chartSpace/c:chart/c:plotArea'; + $element = "$path/c:{$chartType}Chart"; + self::assertTrue($doc->elementExists($path)); + $element = "$path/c:valAx"; + self::assertTrue($doc->elementExists($element)); + $element .= '/c:title/c:tx/c:rich/a:p/a:r/a:t'; + self::assertEquals('Values', $doc->getElement($element)->nodeValue); + } + + public function testNoAxisLabels(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $style = [ + 'width' => 5000000, + 'height' => 5000000, + 'showAxisLabels' => false, + 'showGridX' => true, + 'showGridY' => true, + 'showLegend' => false, + 'valueAxisTitle' => 'Values', + ]; + $chartType = 'line'; + $categories = ['A', 'B', 'C', 'D', 'E']; + $series1 = [1, 3, 2, 5, 4]; + $section->addChart($chartType, $categories, $series1, $style); + $doc = TestHelperDOCX::getDocument($phpWord); + + $index = 1; + $file = "word/charts/chart{$index}.xml"; + $doc->setDefaultFile($file); + $chartType = 'line'; + $path = '/c:chartSpace/c:chart/c:plotArea'; + $element = "$path/c:{$chartType}Chart"; + $element = "$path/c:valAx"; + $element .= '/c:tickLblPos'; + self::assertEquals('none', $doc->getElementAttribute($element, 'val')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php new file mode 100644 index 0000000000..d3128e8007 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php @@ -0,0 +1,74 @@ +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/FormFieldTest.php b/tests/PhpWordTests/Writer/Word2007/Element/FormFieldTest.php new file mode 100644 index 0000000000..52861900e7 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/FormFieldTest.php @@ -0,0 +1,71 @@ +addSection(); + + $section->addFormField('textinput')->setName('MyTextBox'); + $section->addFormField('checkbox')->setDefault(true)->setValue('Your name'); + $section->addFormField('checkbox')->setDefault(true); + $section->addFormField('dropdown')->setEntries(['Choice 1', 'Choice 2', 'Choice 3', '']); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $path = '/w:document/w:body/w:p[1]/w:r/w:fldChar/w:ffData'; + self::assertTrue($doc->elementExists("$path/w:textInput")); + self::assertEquals('MyTextBox', $doc->getElementAttribute("$path/w:name", 'w:val')); + + $path = '/w:document/w:body/w:p[2]/w:r/w:fldChar/w:ffData'; + self::assertTrue($doc->elementExists("$path/w:checkBox")); + $path = '/w:document/w:body/w:p[2]/w:r[4]/w:t'; + self::assertEquals('Your name', $doc->getElement($path)->textContent); + + $path = '/w:document/w:body/w:p[3]/w:r/w:fldChar/w:ffData'; + self::assertTrue($doc->elementExists("$path/w:checkBox")); + + $path = '/w:document/w:body/w:p[4]/w:r/w:fldChar/w:ffData/w:ddList'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals('Choice 1', $doc->getElementAttribute("$path/w:listEntry[1]", 'w:val')); + self::assertEquals('Choice 2', $doc->getElementAttribute("$path/w:listEntry[2]", 'w:val')); + self::assertEquals('Choice 3', $doc->getElementAttribute("$path/w:listEntry[3]", 'w:val')); + self::assertEquals('', trim($doc->getElementAttribute("$path/w:listEntry[4]", 'w:val'), ' ')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Element/FormulaTest.php b/tests/PhpWordTests/Writer/Word2007/Element/FormulaTest.php new file mode 100644 index 0000000000..dc0bd19e22 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/FormulaTest.php @@ -0,0 +1,73 @@ +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..66778a41ec --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php @@ -0,0 +1,89 @@ +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); + } + + public function testWriteTitleWithoutpageNumber(): void + { + $phpWord = new PhpWord(); + + $section = $phpWord->addSection(); + $section->addTOC(); + + $staticHtml = '

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. + Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. + Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi.

    +

    Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. + Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim.

    '; + + //more than one title and random text for create more than one page + for ($i = 1; $i <= 10; ++$i) { + $section->addTitle('Title ' . $i, 1); + \PhpOffice\PhpWord\Shared\Html::addHtml($section, $staticHtml, false, false); + $section->addPageBreak(); + } + + $doc = TestHelperDOCX::getDocument($phpWord); + + for ($i = 1; $i <= 10; ++$i) { + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[' . $i . ']/w:hyperlink/w:r[1]/w:t')); + self::assertEquals('Title ' . $i, $doc->getElement('/w:document/w:body/w:p[' . $i . ']/w:hyperlink/w:r[1]/w:t')->textContent); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[' . $i . ']/w:hyperlink/w:r[4]/w:instrText')); + self::assertEquals('preserve', $doc->getElementAttribute('/w:document/w:body/w:p[' . $i . ']/w:hyperlink/w:r[4]/w:instrText', 'xml:space')); + self::assertEquals('PAGEREF ' . ($i - 1) . ' \\h', $doc->getElement('/w:document/w:body/w:p[' . $i . ']/w:hyperlink/w:r[4]/w:instrText')->nodeValue); + } + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php b/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php new file mode 100644 index 0000000000..8b9e5efee3 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php @@ -0,0 +1,148 @@ +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/TextBoxTest.php b/tests/PhpWordTests/Writer/Word2007/Element/TextBoxTest.php new file mode 100644 index 0000000000..6d4df65298 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/TextBoxTest.php @@ -0,0 +1,84 @@ +setBgColor($bgColor); + } + + if ($borderColor !== null) { + $style->setBorderColor($borderColor); + } + + $textBoxElement = new TextBoxElement($style); + $textBox = new TextBox($xmlWriter, $textBoxElement); + + // Act + $textBox->write(); + $output = $xmlWriter->getData(); + + // Assert + self::assertStringContainsString($expectedFillColorAttribute, $output, 'Background color should be applied.'); + self::assertStringContainsString($expectedBorderColorAttribute, $output, 'Border color should be applied correctly.'); + } + + /** + * Data provider for testing different combinations of background and border colors. + */ + public static function textBoxColorProvider(): array + { + return [ + // Case 1: Background color set, border color set + 'With both colors' => [ + '#FF0000', + '#000000', + 'fillcolor="#FF0000"', + 'stroke color="#000000"', + ], + // Case 2: Background color set, no border color + 'With background only' => [ + '#00FF00', + null, + 'fillcolor="#00FF00"', + 'stroked="f" strokecolor="white"', + ], + // Case 3: No background color, border color set + 'With border only' => [ + null, + '#123456', + 'filled="f"', + 'stroke color="#123456"', + ], + // Case 4: Neither background nor border color set + 'Without any colors' => [ + null, + null, + 'filled="f"', + 'stroked="f" strokecolor="white"', + ], + ]; + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Element/TitleTest.php b/tests/PhpWordTests/Writer/Word2007/Element/TitleTest.php new file mode 100644 index 0000000000..1d62ffa964 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/TitleTest.php @@ -0,0 +1,97 @@ +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 new file mode 100644 index 0000000000..c22994607b --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/ElementTest.php @@ -0,0 +1,667 @@ +write(); + + self::assertEquals('', $xmlWriter->getData()); + } + } + + /** + * Test line element. + */ + public function testLineElement(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $section->addLine(['width' => 1000, 'height' => 1000, 'positioning' => 'absolute', 'flip' => true]); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p/w:r/w:pict/v:shapetype'; + self::assertTrue($doc->elementExists($element)); + } + + /** + * Test bookmark element. + */ + public function testBookmark(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $section->addBookmark('test_bookmark'); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:bookmarkStart'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('test_bookmark', $doc->getElementAttribute($element, 'w:name')); + + $element = '/w:document/w:body/w:bookmarkEnd'; + self::assertTrue($doc->elementExists($element)); + } + + /** + * Test link element. + */ + public function testLinkElement(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $section->addLink('https://github.com/PHPOffice/PHPWord'); + $section->addLink('internal_link', null, null, null, true); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p[1]/w:hyperlink/w:r/w:t'; + self::assertTrue($doc->elementExists($element)); + + $element = '/w:document/w:body/w:p[2]/w:hyperlink/w:r/w:t'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('internal_link', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:hyperlink', 'w:anchor')); + } + + /** + * Basic test for table element. + */ + public function testTableElements(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $table = $section->addTable(['alignment' => \PhpOffice\PhpWord\SimpleType\JcTable::CENTER]); + $table->addRow(900); + $table->addCell(2000)->addText('Row 1'); + $table->addCell(2000)->addText('Row 2'); + $table->addCell(2000)->addText('Row 3'); + $table->addCell(2000)->addText('Row 4'); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $tableRootElement = '/w:document/w:body/w:tbl'; + self::assertTrue($doc->elementExists($tableRootElement . '/w:tblGrid/w:gridCol')); + self::assertTrue($doc->elementExists($tableRootElement . '/w:tblPr/w:jc')); + self::assertEquals('center', $doc->getElementAttribute($tableRootElement . '/w:tblPr/w:jc', 'w:val')); + } + + /** + * Tests that the style name gets added. + */ + public function testTableWithStyleName(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $table = $section->addTable('my_predefined_style'); + $table->setWidth(75); + $table->addRow(900); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $tableRootElement = '/w:document/w:body/w:tbl'; + self::assertTrue($doc->elementExists($tableRootElement . '/w:tblPr/w:tblStyle')); + self::assertEquals('my_predefined_style', $doc->getElementAttribute($tableRootElement . '/w:tblPr/w:tblStyle', 'w:val')); + } + + /** + * Test shape elements. + */ + public function testShapeElements(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + // Arc + $section->addShape( + 'arc', + [ + 'points' => '-90 20', + 'frame' => ['width' => 120, 'height' => 120], + 'outline' => ['color' => '#333333', 'weight' => 2, 'startArrow' => 'oval', 'endArrow' => 'open'], + ] + ); + + // Curve + $section->addShape( + 'curve', + [ + 'points' => '1,100 200,1 1,50 200,50', 'connector' => 'elbow', + 'outline' => [ + 'color' => '#66cc00', + 'weight' => 2, + 'dash' => 'dash', + 'startArrow' => 'diamond', + 'endArrow' => 'block', + ], + ] + ); + + // Line + $section->addShape( + 'line', + [ + 'points' => '1,1 150,30', + 'outline' => [ + 'color' => '#cc00ff', + 'line' => 'thickThin', + 'weight' => 3, + 'startArrow' => 'oval', + 'endArrow' => 'classic', + 'endCap' => 'round', + ], + ] + ); + + // Polyline + $section->addShape( + 'polyline', + [ + 'points' => '1,30 20,10 55,20 75,10 100,40 115,50, 120,15 200,50', + 'outline' => [ + 'color' => '#cc6666', + 'weight' => 2, + 'startArrow' => 'none', + 'endArrow' => 'classic', + ], + ] + ); + + // Rectangle + $section->addShape( + 'rect', + [ + 'roundness' => 0.2, + 'frame' => ['width' => 100, 'height' => 100, 'left' => 1, 'top' => 1], + 'fill' => ['color' => '#FFCC33'], + 'outline' => ['color' => '#990000', 'weight' => 1], + 'shadow' => ['color' => '#EEEEEE', 'offset' => '3pt,3pt'], + ] + ); + + // Oval + $section->addShape( + 'oval', + [ + 'frame' => ['width' => 100, 'height' => 70, 'left' => 1, 'top' => 1], + 'fill' => ['color' => '#33CC99'], + 'outline' => ['color' => '#333333', 'weight' => 2], + 'extrusion' => ['type' => 'perspective', 'color' => '#EEEEEE'], + ] + ); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $elements = ['arc', 'curve', 'line', 'polyline', 'roundrect', 'oval']; + foreach ($elements as $element) { + $path = "/w:document/w:body/w:p/w:r/w:pict/v:{$element}"; + self::assertTrue($doc->elementExists($path)); + } + } + + // testChartElements moved to Writer/Word2007/Element/ChartTest + + public function testFieldElement(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $section->addField('INDEX', [], ['\\c "3"']); + $section->addField('XE', [], ['Bold', 'Italic'], 'Index Entry'); + $section->addField('DATE', ['dateformat' => 'd-M-yyyy'], ['PreserveFormat', 'LastUsedFormat']); + $section->addField('DATE', [], ['LunarCalendar']); + $section->addField('DATE', [], ['SakaEraCalendar']); + $section->addField('NUMPAGES', ['format' => 'roman', 'numformat' => '0,00'], ['SakaEraCalendar']); + $section->addField('STYLEREF', ['StyleIdentifier' => 'Heading 1']); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p/w:r/w:instrText'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals(' INDEX \\c "3" ', $doc->getElement($element)->textContent); + } + + public function testUnstyledFieldElement(): void + { + $phpWord = new PhpWord(); + $phpWord->addFontStyle('h1', ['name' => 'Courier New', 'size' => 8]); + $section = $phpWord->addSection(); + + $section->addField('PAGE'); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p/w:r[2]/w:instrText'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals(' PAGE ', $doc->getElement($element)->textContent); + $sty = '/w:document/w:body/w:p/w:r[2]/w:rPr'; + self::assertFalse($doc->elementExists($sty)); + } + + public function testStyledFieldElement(): void + { + $phpWord = new PhpWord(); + $stnam = 'h1'; + $phpWord->addFontStyle($stnam, ['name' => 'Courier New', 'size' => 8]); + $section = $phpWord->addSection(); + + $fld = $section->addField('PAGE'); + $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(' PAGE ', $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 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(); + $section = $phpWord->addSection(); + + $text = new TextRun(); + $text->addText('test string', ['bold' => true]); + + $section->addField('XE', [], ['Bold', 'Italic'], $text); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p/w:r[2]/w:instrText'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals(' XE "', $doc->getElement($element)->textContent); + + $element = '/w:document/w:body/w:p/w:r[3]/w:rPr/w:b'; + self::assertTrue($doc->elementExists($element)); + + $element = '/w:document/w:body/w:p/w:r[3]/w:t'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('test string', $doc->getElement($element)->textContent); + + $element = '/w:document/w:body/w:p/w:r[4]/w:instrText'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('"\\b \\i ', $doc->getElement($element)->textContent); + } + + /** + * Test writing the macrobutton field. + */ + public function testMacroButtonField(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $macroText = new TextRun(); + $macroText->addText('Double click', ['bold' => true]); + $macroText->addText(' to '); + $macroText->addText('zoom to 100%', ['italic' => true]); + + $section->addField('MACROBUTTON', ['macroname' => 'Zoom100'], [], $macroText); + $section->addField('MACROBUTTON', ['macroname' => 'Zoom100'], [], 'double click to zoom'); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p[1]/w:r[2]/w:instrText'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals(' MACROBUTTON Zoom100 ', $doc->getElement($element)->textContent); + + $element = '/w:document/w:body/w:p[1]/w:r[3]/'; + self::assertTrue($doc->elementExists($element . 'w:t')); + self::assertEquals('Double click', $doc->getElement($element . 'w:t')->textContent); + self::assertTrue($doc->elementExists($element . 'w:rPr/w:b')); + + $element = '/w:document/w:body/w:p[2]/w:r[2]/w:instrText'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals(' MACROBUTTON Zoom100 double click to zoom ', $doc->getElement($element)->textContent); + } + + // testFormFieldElements moved to Writer/Word2007/Element/FormFieldTest + + /** + * Test SDT elements. + */ + public function testSDTElements(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $section->addSDT('comboBox')->setListItems(['1' => 'Choice 1', '2' => 'Choice 2'])->setValue('select value'); + $section->addSDT('dropDownList'); + $section->addSDT('date')->setAlias('date_alias')->setTag('my_tag'); + $section->addSDT('plainText'); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $path = '/w:document/w:body/w:p'; + + self::assertTrue($doc->elementExists($path . '[1]/w:sdt/w:sdtContent/w:r/w:t')); + self::assertEquals('select value', $doc->getElement($path . '[1]/w:sdt/w:sdtContent/w:r/w:t')->nodeValue); + self::assertTrue($doc->elementExists($path . '[1]/w:sdt/w:sdtPr/w:comboBox')); + self::assertTrue($doc->elementExists($path . '[1]/w:sdt/w:sdtPr/w:comboBox/w:listItem')); + self::assertEquals('1', $doc->getElementAttribute($path . '[1]/w:sdt/w:sdtPr/w:comboBox/w:listItem[1]', 'w:value')); + self::assertEquals('Choice 1', $doc->getElementAttribute($path . '[1]/w:sdt/w:sdtPr/w:comboBox/w:listItem[1]', 'w:displayText')); + + self::assertTrue($doc->elementExists($path . '[2]/w:sdt/w:sdtPr/w:dropDownList')); + self::assertFalse($doc->elementExists($path . '[2]/w:sdt/w:sdtPr/w:alias')); + + self::assertTrue($doc->elementExists($path . '[3]/w:sdt/w:sdtPr/w:date')); + self::assertTrue($doc->elementExists($path . '[3]/w:sdt/w:sdtPr/w:alias')); + self::assertTrue($doc->elementExists($path . '[3]/w:sdt/w:sdtPr/w:tag')); + + self::assertTrue($doc->elementExists($path . '[4]/w:sdt/w:sdtPr/w:text')); + } + + /** + * Test Comment element. + */ + public function testCommentWithoutEndElement(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $comment = new Comment('tester'); + $phpWord->addComment($comment); + + $element = $section->addText('this is a test'); + $element->setCommentRangeStart($comment); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:commentRangeStart')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:commentRangeEnd')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:commentReference')); + } + + /** + * Test Comment element. + */ + public function testCommentWithEndElement(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $comment = new Comment('tester'); + $phpWord->addComment($comment); + + $element = $section->addText('this is a test'); + $element->setCommentRangeStart($comment); + $element->setCommentRangeEnd($comment); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:commentRangeStart')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:commentRangeEnd')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:commentReference')); + } + + /** + * Test Track changes. + */ + public function testTrackChange(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $text = $section->addText('my dummy text'); + $text->setChangeInfo(TrackChange::INSERTED, 'author name'); + $text2 = $section->addText('my other text'); + $text2->setTrackChange(new TrackChange(TrackChange::DELETED, 'another author', new DateTime())); + + $doc = TestHelperDOCX::getDocument($phpWord); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:ins/w:r')); + self::assertEquals('author name', $doc->getElementAttribute('/w:document/w:body/w:p/w:ins', 'w:author')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:del/w:r/w:delText')); + } + + /** + * Test correct writing of text with ampersant in it. + */ + public function testTextWithAmpersant(): void + { + \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('this text contains an & (ampersant)'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t')); + self::assertEquals('this text contains an & (ampersant)', $doc->getElement('/w:document/w:body/w:p/w:r/w:t')->nodeValue); + } + + /** + * Test ListItemRun paragraph style writing. + */ + public function testListItemRunStyleWriting(): void + { + $phpWord = new PhpWord(); + $phpWord->addParagraphStyle('MyParagraphStyle', ['spaceBefore' => 400]); + + $section = $phpWord->addSection(); + $listItemRun = $section->addListItemRun(0, null, 'MyParagraphStyle'); + $listItemRun->addText('List item'); + $listItemRun->addText(' in bold', ['bold' => true]); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:pPr')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:pStyle')); + self::assertEquals('List item', $doc->getElement('/w:document/w:body/w:p/w:r[1]/w:t')->nodeValue); + self::assertEquals(' in bold', $doc->getElement('/w:document/w:body/w:p/w:r[2]/w:t')->nodeValue); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r[2]/w:rPr/w:b')); + } + + /** + * Test Ruby writing. + */ + public function testRubyWriting(): void + { + $phpWord = new PhpWord(); + + $section = $phpWord->addSection(); + $properties = new RubyProperties(); + $properties->setAlignment(RubyProperties::ALIGNMENT_RIGHT_VERTICAL); + $properties->setFontFaceSize(10); + $properties->setFontPointsAboveBaseText(4); + $properties->setFontSizeForBaseText(18); + $properties->setLanguageId('ja-JP'); + + $baseTextRun = new TextRun(null); + $baseTextRun->addText('私'); + $rubyTextRun = new TextRun(null); + $rubyTextRun->addText('わたし'); + $section->addRuby($baseTextRun, $rubyTextRun, $properties); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby')); + // check props + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr')); + self::assertEquals( + RubyProperties::ALIGNMENT_RIGHT_VERTICAL, + $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:rubyAlign', 'w:val') + ); + self::assertEquals( + 10, + $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hps', 'w:val') + ); + self::assertEquals( + 4, + $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hpsRaise', 'w:val') + ); + self::assertEquals( + 18, + $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hpsBaseText', 'w:val') + ); + self::assertEquals( + 'ja-JP', + $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:lid', 'w:val') + ); + // check ruby text + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rt/w:r/w:t')); + self::assertEquals( + 'わたし', + $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rt/w:r/w:t')->nodeValue + ); + // check base text + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyBase/w:r/w:t')); + self::assertEquals( + '私', + $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rubyBase/w:r/w:t')->nodeValue + ); + } + + /** + * Test Ruby title writing. + */ + public function testRubyTitleWriting(): void + { + $phpWord = new PhpWord(); + $phpWord->addTitleStyle(1, ['name' => 'Arial', 'size' => 24, 'bold' => true, 'color' => '990000']); + + $section = $phpWord->addSection(); + $properties = new RubyProperties(); + $properties->setAlignment(RubyProperties::ALIGNMENT_RIGHT_VERTICAL); + $properties->setFontFaceSize(10); + $properties->setFontPointsAboveBaseText(4); + $properties->setFontSizeForBaseText(18); + $properties->setLanguageId('ja-JP'); + + $baseTextRun = new TextRun(null); + $baseTextRun->addText('私'); + $rubyTextRun = new TextRun(null); + $rubyTextRun->addText('わたし'); + + $textRun = new TextRun(); + $textRun->addRuby($baseTextRun, $rubyTextRun, $properties); + $section->addTitle($textRun, 1); + + $doc = TestHelperDOCX::getDocument($phpWord); + // make sure style made it in + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:pStyle')); + self::assertEquals( + 'Heading1', + $doc->getElement('/w:document/w:body/w:p/w:pPr/w:pStyle')->getAttribute('w:val') + ); + // check ruby props + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr')); + self::assertEquals( + RubyProperties::ALIGNMENT_RIGHT_VERTICAL, + $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:rubyAlign')->getAttribute('w:val') + ); + self::assertEquals( + 10, + $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hps')->getAttribute('w:val') + ); + self::assertEquals( + 4, + $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hpsRaise')->getAttribute('w:val') + ); + self::assertEquals( + 18, + $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hpsBaseText')->getAttribute('w:val') + ); + self::assertEquals( + 'ja-JP', + $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:lid')->getAttribute('w:val') + ); + // check ruby text + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rt/w:r/w:t')); + self::assertEquals( + 'わたし', + $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rt/w:r/w:t')->nodeValue + ); + // check base text + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyBase/w:r/w:t')); + self::assertEquals( + '私', + $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rubyBase/w:r/w:t')->nodeValue + ); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php new file mode 100644 index 0000000000..1bcd43f50e --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php @@ -0,0 +1,72 @@ +getMockForAbstractClass(Word2007\Part\AbstractPart::class); + } else { + /** @var Word2007\Part\AbstractPart $stub */ + $stub = new class() extends Word2007\Part\AbstractPart { + public function write(): string + { + return ''; + } + }; + } + $stub->setParentWriter(new Word2007()); + self::assertEquals(new Word2007(), $stub->getParentWriter()); + } + + /** + * covers ::getParentWriter. + */ + public function testSetGetParentWriterNull(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('No parent WriterInterface assigned.'); + // @phpstan-ignore-next-line + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(Word2007\Part\AbstractPart::class); + } else { + /** @var Word2007\Part\AbstractPart $stub */ + $stub = new class() extends Word2007\Part\AbstractPart { + public function write(): string + { + return ''; + } + }; + } + $stub->getParentWriter(); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Part/CommentsTest.php b/tests/PhpWordTests/Writer/Word2007/Part/CommentsTest.php new file mode 100644 index 0000000000..8f8c8240a9 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Part/CommentsTest.php @@ -0,0 +1,62 @@ +addText('Test'); + + $phpWord = new PhpWord(); + $phpWord->addComment($comment); + $doc = TestHelperDOCX::getDocument($phpWord); + + $path = '/w:comments/w:comment'; + $file = 'word/comments.xml'; + + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertNotNull($element->getAttribute('w:id')); + self::assertEquals('Authors name', $element->getAttribute('w:author')); + self::assertEquals('my_initials', $element->getAttribute('w:initials')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php new file mode 100644 index 0000000000..d1f5b2585d --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php @@ -0,0 +1,710 @@ +getDocInfo(); + + $docInfo->setCustomProperty('key1', null); + $docInfo->setCustomProperty('key2', true); + $docInfo->setCustomProperty('key3', 3); + $docInfo->setCustomProperty('key4', 4.4); + $docInfo->setCustomProperty('key5', 'value5'); + $docInfo->setCustomProperty('key6', new DateTime()); + $docInfo->setCustomProperty('key7', time(), DocInfo::PROPERTY_TYPE_DATE); + + $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')); + } + + /** + * Write end section page numbering. + */ + public function testWriteEndSectionPageNumbering(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addHeader(); + $section->addHeader('first'); + $style = $section->getStyle(); + $style->setLandscape(); + $style->setPageNumberingStart(2); + $style->setBorderSize(240); + $style->setBreakType('nextPage'); + + $doc = TestHelperDOCX::getDocument($phpWord); + $element = $doc->getElement('/w:document/w:body/w:sectPr/w:pgNumType'); + + self::assertEquals(2, $element->getAttribute('w:start')); + } + + /** + * Write section footnote properties. + */ + public function testSectionFootnoteProperties(): void + { + $properties = new FootnoteProperties(); + $properties->setPos(FootnoteProperties::POSITION_DOC_END); + $properties->setNumFmt(NumberFormat::LOWER_ROMAN); + $properties->setNumStart(1); + $properties->setNumRestart(FootnoteProperties::RESTART_NUMBER_EACH_PAGE); + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->setFootnoteProperties($properties); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = $doc->getElement('/w:document/w:body/w:sectPr/w:footnotePr/w:pos'); + self::assertEquals(FootnoteProperties::POSITION_DOC_END, $element->getAttribute('w:val')); + + $element = $doc->getElement('/w:document/w:body/w:sectPr/w:footnotePr/w:numFmt'); + self::assertEquals(NumberFormat::LOWER_ROMAN, $element->getAttribute('w:val')); + + $element = $doc->getElement('/w:document/w:body/w:sectPr/w:footnotePr/w:numStart'); + self::assertEquals(1, $element->getAttribute('w:val')); + + $element = $doc->getElement('/w:document/w:body/w:sectPr/w:footnotePr/w:numRestart'); + self::assertEquals(FootnoteProperties::RESTART_NUMBER_EACH_PAGE, $element->getAttribute('w:val')); + } + + /** + * Write elements. + */ + public function testElements(): void + { + $objectSrc = __DIR__ . '/../../../_files/documents/sheet.xls'; + + $phpWord = new PhpWord(); + $phpWord->addTitleStyle(1, ['color' => '333333', 'bold' => true]); + $phpWord->addTitleStyle(2, ['color' => '666666']); + $section = $phpWord->addSection(); + $section->addTOC(); + $section->addPageBreak(); + $section->addText('After page break.'); + $section->addTitle('Title 1', 1); + $section->addListItem('List Item 1', 0); + $section->addListItem('List Item 2', 0); + $section->addListItem('List Item 3', 0); + + $section = $phpWord->addSection(); + $section->addTitle('Title 2', 2); + $section->addObject($objectSrc); + $section->addTextBox([]); + $section->addTextBox( + [ + 'wrappingStyle' => 'square', + 'positioning' => 'relative', + 'posHorizontalRel' => 'margin', + 'posVerticalRel' => 'margin', + 'innerMargin' => 10, + 'borderSize' => 1, + 'borderColor' => '#FF0', + ] + ); + $section->addTextBox(['wrappingStyle' => 'tight', 'positioning' => 'absolute', 'alignment' => Jc::CENTER]); + $section->addListItemRun()->addText('List item run 1'); + $section->addField( + 'DATE', + ['dateformat' => 'dddd d MMMM yyyy H:mm:ss'], + ['PreserveFormat', 'LunarCalendar'] + ); + $section->addField( + 'DATE', + ['dateformat' => 'dddd d MMMM yyyy H:mm:ss'], + ['PreserveFormat', 'SakaEraCalendar'] + ); + $section->addField( + 'DATE', + ['dateformat' => 'dddd d MMMM yyyy H:mm:ss'], + ['PreserveFormat', 'LastUsedFormat'] + ); + $section->addField('PAGE', ['format' => 'ArabicDash']); + $section->addLine( + [ + 'width' => 10, + 'height' => 10, + 'positioning' => 'absolute', + 'beginArrow' => 'block', + 'endArrow' => 'open', + 'dash' => 'rounddot', + 'weight' => 10, + ] + ); + + $doc = TestHelperDOCX::getDocument($phpWord); + + // TOC + $element = $doc->getElement('/w:document/w:body/w:p[1]/w:pPr/w:tabs/w:tab'); + self::assertEquals('right', $element->getAttribute('w:val')); + self::assertEquals('dot', $element->getAttribute('w:leader')); + self::assertEquals(9062, $element->getAttribute('w:pos')); + + // Page break + $element = $doc->getElement('/w:document/w:body/w:p[4]/w:r/w:br'); + self::assertEquals('page', $element->getAttribute('w:type')); + + // Title + $element = $doc->getElement('/w:document/w:body/w:p[6]/w:pPr/w:pStyle'); + self::assertEquals('Heading1', $element->getAttribute('w:val')); + + // List item + $element = $doc->getElement('/w:document/w:body/w:p[7]/w:pPr/w:numPr/w:numId'); + self::assertEquals(3, $element->getAttribute('w:val')); + + // Object + $element = $doc->getElement('/w:document/w:body/w:p[12]/w:r/w:object/o:OLEObject'); + self::assertEquals('Embed', $element->getAttribute('Type')); + } + + /** + * Write element with some styles. + */ + public function testElementStyles(): void + { + $objectSrc = __DIR__ . '/../../../_files/documents/sheet.xls'; + + $tabs = [new \PhpOffice\PhpWord\Style\Tab('right', 9090)]; + $phpWord = new PhpWord(); + $phpWord->addParagraphStyle( + 'pStyle', + [ + 'alignment' => Jc::CENTER, + 'tabs' => $tabs, + 'shading' => ['fill' => 'FFFF99'], + 'borderSize' => 4, + ] + ); // Style #1 + $phpWord->addFontStyle( + 'fStyle', + [ + 'size' => '20', + 'bold' => true, + 'allCaps' => true, + 'scale' => 200, + 'spacing' => 240, + 'kerning' => 10, + ] + ); // Style #2 + $phpWord->addTitleStyle(1, ['color' => '333333', 'doubleStrikethrough' => true]); // Style #3 + $phpWord->addTableStyle('tStyle', ['borderSize' => 1]); + $fontStyle = new Font('text', ['alignment' => Jc::CENTER]); + + $section = $phpWord->addSection(); + $section->addListItem('List Item', 0, null, null, 'pStyle'); // Style #5 + $section->addObject($objectSrc, ['alignment' => Jc::CENTER]); + $section->addTOC($fontStyle); + $section->addTitle('Title 1', 1); + $section->addTOC('fStyle'); + $table = $section->addTable('tStyle'); + $table->setWidth(100); + $doc = TestHelperDOCX::getDocument($phpWord); + + // List item + $element = $doc->getElement('/w:document/w:body/w:p[1]/w:pPr/w:numPr/w:numId'); + self::assertEquals(5, $element->getAttribute('w:val')); + + // Object + $element = $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:object/o:OLEObject'); + self::assertEquals('Embed', $element->getAttribute('Type')); + + // TOC + $element = $doc->getElement('/w:document/w:body/w:p[3]/w:pPr/w:tabs/w:tab'); + self::assertEquals('right', $element->getAttribute('w:val')); + self::assertEquals('dot', $element->getAttribute('w:leader')); + self::assertEquals(9062, $element->getAttribute('w:pos')); + } + + /** + * Test write text element. + */ + public function testWriteText(): void + { + $rStyle = 'rStyle'; + $pStyle = 'pStyle'; + + $phpWord = new PhpWord(); + $phpWord->addFontStyle($rStyle, ['bold' => true]); + $phpWord->addParagraphStyle($pStyle, ['hanging' => 120, 'indent' => 120]); + $section = $phpWord->addSection(); + $section->addText('Test', $rStyle, $pStyle); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p/w:r/w:rPr/w:rStyle'; + self::assertEquals($rStyle, $doc->getElementAttribute($element, 'w:val')); + $element = '/w:document/w:body/w:p/w:pPr/w:pStyle'; + self::assertEquals($pStyle, $doc->getElementAttribute($element, 'w:val')); + } + + /** + * Test write textrun element. + */ + public function testWriteTextRun(): void + { + $pStyle = 'pStyle'; + $aStyle = ['alignment' => Jc::BOTH, 'spaceBefore' => 120, 'spaceAfter' => 120]; + $imageSrc = __DIR__ . '/../../../_files/images/earth.jpg'; + + $phpWord = new PhpWord(); + $phpWord->addParagraphStyle($pStyle, $aStyle); + $section = $phpWord->addSection('Test'); + $textrun = $section->addTextRun($pStyle); + $textrun->addText('Test'); + $textrun->addTextBreak(); + $textrun = $section->addTextRun($aStyle); + $textrun->addLink('https://github.com/PHPOffice/PHPWord'); + $textrun->addImage($imageSrc, ['alignment' => Jc::CENTER]); + $textrun->addFootnote(); + $doc = TestHelperDOCX::getDocument($phpWord); + + $parent = '/w:document/w:body/w:p'; + self::assertTrue($doc->elementExists("{$parent}/w:pPr/w:pStyle[@w:val='{$pStyle}']")); + } + + /** + * Test write link element. + */ + public function testWriteLink(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $fontStyleArray = ['bold' => true]; + $fontStyleName = 'Font Style'; + $paragraphStyleArray = ['alignment' => Jc::CENTER]; + $paragraphStyleName = 'Paragraph Style'; + + $expected = 'PHPWord on GitHub'; + $section->addLink('https://github.com/PHPOffice/PHPWord', $expected); + $section->addLink('https://github.com/PHPOffice/PHPWord', 'Test', $fontStyleArray, $paragraphStyleArray); + $section->addLink('https://github.com/PHPOffice/PHPWord', 'Test', $fontStyleName, $paragraphStyleName); + + $doc = TestHelperDOCX::getDocument($phpWord); + $element = $doc->getElement('/w:document/w:body/w:p/w:hyperlink/w:r/w:t'); + + self::assertEquals($expected, $element->nodeValue); + } + + /** + * Test write preserve text element. + */ + public function testWritePreserveText(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $footer = $section->addFooter(); + $fontStyleArray = ['bold' => true]; + $fontStyleName = 'Font'; + $paragraphStyleArray = ['alignment' => Jc::END]; + $paragraphStyleName = 'Paragraph'; + + $footer->addPreserveText('Page {PAGE}'); + $footer->addPreserveText('{PAGE}', $fontStyleArray, $paragraphStyleArray); + $footer->addPreserveText('{PAGE}', $fontStyleName, $paragraphStyleName); + + $doc = TestHelperDOCX::getDocument($phpWord); + $preserve = $doc->getElement('w:p/w:r[2]/w:instrText', 'word/footer1.xml'); + + self::assertEquals('PAGE', $preserve->nodeValue); + self::assertEquals('preserve', $preserve->getAttribute('xml:space')); + } + + /** + * Test write text break. + */ + public function testWriteTextBreak(): void + { + $fArray = ['size' => 12]; + $pArray = ['spacing' => 240]; + $fName = 'fStyle'; + $pName = 'pStyle'; + + $phpWord = new PhpWord(); + $phpWord->addFontStyle($fName, $fArray); + $phpWord->addParagraphStyle($pName, $pArray); + $section = $phpWord->addSection(); + $section->addTextBreak(); + $section->addTextBreak(1, $fArray, $pArray); + $section->addTextBreak(1, $fName, $pName); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:rPr/w:rStyle'); + self::assertEquals($fName, $element->getAttribute('w:val')); + $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:pStyle'); + self::assertEquals($pName, $element->getAttribute('w:val')); + } + + /** + * covers ::_writeImage. + */ + public function testWriteImage(): void + { + $phpWord = new PhpWord(); + $styles = ['alignment' => Jc::START, 'width' => 40, 'height' => 40, 'marginTop' => -1, 'marginLeft' => -1]; + $wraps = ['inline', 'behind', 'infront', 'square', 'tight']; + $section = $phpWord->addSection(); + foreach ($wraps as $wrap) { + $styles['wrappingStyle'] = $wrap; + $section->addImage(__DIR__ . '/../../../_files/images/earth.jpg', $styles); + } + + $archiveFile = realpath(__DIR__ . '/../../../_files/documents/reader.docx'); + $imageFile = 'word/media/image1.jpeg'; + $source = 'zip://' . $archiveFile . '#' . $imageFile; + $section->addImage($source); + + $doc = TestHelperDOCX::getDocument($phpWord); + + // behind + $element = $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:pict/v:shape'); + $style = $element->getAttribute('style'); + // @phpstan-ignore-next-line + if (method_exists(self::class, 'assertMatchesRegularExpression')) { + self::assertMatchesRegularExpression('/z\-index:\-[0-9]*/', $style); + } elseif (method_exists(self::class, 'assertRegExp')) { // @phpstan-ignore-line + 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'); + self::assertEquals('square', $element->getAttribute('type')); + } + + /** + * covers ::_writeWatermark. + */ + public function testWriteWatermark(): void + { + $imageSrc = __DIR__ . '/../../../_files/images/earth.jpg'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $header = $section->addHeader(); + $header->addWatermark($imageSrc); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = $doc->getElement('/w:document/w:body/w:sectPr/w:headerReference'); + self::assertStringStartsWith('rId', $element->getAttribute('r:id')); + } + + /** + * covers ::_writeTitle. + */ + public function testWriteTitle(): void + { + $phpWord = new PhpWord(); + $phpWord->addTitleStyle(1, ['bold' => true], ['spaceAfter' => 240]); + $phpWord->addSection()->addTitle('Test', 1); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p/w:pPr/w:pStyle'; + self::assertEquals('Heading1', $doc->getElementAttribute($element, 'w:val')); + } + + /** + * covers ::_writeCheckbox. + */ + public function testWriteCheckbox(): void + { + $rStyle = 'rStyle'; + $pStyle = 'pStyle'; + + $phpWord = new PhpWord(); + // $phpWord->addFontStyle($rStyle, array('bold' => true)); + // $phpWord->addParagraphStyle($pStyle, array('hanging' => 120, 'indent' => 120)); + $section = $phpWord->addSection(); + $section->addCheckBox('Check1', 'Test', $rStyle, $pStyle); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p/w:r/w:fldChar/w:ffData/w:name'; + self::assertEquals('Check1', $doc->getElementAttribute($element, 'w:val')); + } + + /** + * covers ::_writeParagraphStyle. + */ + public function testWriteParagraphStyle(): void + { + // Create the doc + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $attributes = [ + 'alignment' => Jc::END, + 'widowControl' => false, + 'keepNext' => true, + 'keepLines' => true, + 'pageBreakBefore' => true, + ]; + foreach ($attributes as $attribute => $value) { + $section->addText('Test', null, [$attribute => $value]); + } + $doc = TestHelperDOCX::getDocument($phpWord); + + // Test the attributes + $attributeCount = 0; + foreach ($attributes as $key => $value) { + ++$attributeCount; + $nodeName = ($key == 'alignment') ? 'jc' : $key; + $path = "/w:document/w:body/w:p[{$attributeCount}]/w:pPr/w:{$nodeName}"; + if ('alignment' != $key) { + $value = $value ? 1 : 0; + } + $element = $doc->getElement($path); + self::assertEquals($value, $element->getAttribute('w:val')); + } + } + + /** + * covers ::_writeTextStyle. + */ + public function testWriteFontStyle(): void + { + $phpWord = new PhpWord(); + $styles['name'] = 'Verdana'; + $styles['size'] = 14; + $styles['bold'] = true; + $styles['italic'] = true; + $styles['underline'] = 'dash'; + $styles['strikethrough'] = true; + $styles['superScript'] = true; + $styles['color'] = 'FF0000'; + $styles['fgColor'] = 'yellow'; + $styles['bgColor'] = 'FFFF00'; + $styles['hint'] = 'eastAsia'; + $styles['smallCaps'] = true; + + $section = $phpWord->addSection(); + $section->addText('Test', $styles); + $doc = TestHelperDOCX::getDocument($phpWord); + + $parent = '/w:document/w:body/w:p/w:r/w:rPr'; + self::assertEquals($styles['name'], $doc->getElementAttribute("{$parent}/w:rFonts", 'w:ascii')); + self::assertEquals($styles['size'] * 2, $doc->getElementAttribute("{$parent}/w:sz", 'w:val')); + self::assertTrue($doc->elementExists("{$parent}/w:b")); + 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. + */ + public function testWriteDefaultColor(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $cStyles['borderTopSize'] = 120; + + $table = $section->addTable(); + $table->addRow(); + $cell = $table->addCell(null, $cStyles); + $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' + ) + ); + } + + /** + * covers ::_writeTableStyle. + */ + public function testWriteTableStyle(): void + { + $phpWord = new PhpWord(); + $rHeight = 120; + $cWidth = 120; + $imageSrc = __DIR__ . '/../../../_files/images/earth.jpg'; + $objectSrc = __DIR__ . '/../../../_files/documents/sheet.xls'; + + $tStyles['width'] = 50; + $tStyles['cellMarginTop'] = 120; + $tStyles['cellMarginRight'] = 120; + $tStyles['cellMarginBottom'] = 120; + $tStyles['cellMarginLeft'] = 120; + $rStyles['tblHeader'] = true; + $rStyles['cantSplit'] = true; + $cStyles['valign'] = 'top'; + $cStyles['textDirection'] = 'btLr'; + $cStyles['bgColor'] = 'FF0000'; + $cStyles['borderTopSize'] = 120; + $cStyles['borderBottomSize'] = 120; + $cStyles['borderLeftSize'] = 120; + $cStyles['borderRightSize'] = 120; + $cStyles['borderTopColor'] = 'FF0000'; + $cStyles['borderBottomColor'] = 'FF0000'; + $cStyles['borderLeftColor'] = 'FF0000'; + $cStyles['borderRightColor'] = 'FF0000'; + $cStyles['vMerge'] = 'restart'; + + $section = $phpWord->addSection(); + $table = $section->addTable($tStyles); + $table->setWidth(100); + $table->addRow($rHeight, $rStyles); + $cell = $table->addCell($cWidth, $cStyles); + $cell->addText('Test'); + $cell->addTextBreak(); + $cell->addLink('https://github.com/PHPOffice/PHPWord'); + $cell->addListItem('Test'); + $cell->addImage($imageSrc); + $cell->addObject($objectSrc); + $textrun = $cell->addTextRun(); + $textrun->addText('Test'); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $parent = '/w:document/w:body/w:tbl/w:tblPr/w:tblCellMar'; + + $parent = '/w:document/w:body/w:tbl/w:tr/w:trPr'; + self::assertEquals($rHeight, $doc->getElementAttribute("{$parent}/w:trHeight", 'w:val')); + self::assertEquals($rStyles['tblHeader'], $doc->getElementAttribute("{$parent}/w:tblHeader", 'w:val')); + self::assertEquals($rStyles['cantSplit'], $doc->getElementAttribute("{$parent}/w:cantSplit", 'w:val')); + + $parent = '/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr'; + self::assertEquals($cWidth, $doc->getElementAttribute("{$parent}/w:tcW", 'w:w')); + self::assertEquals($cStyles['valign'], $doc->getElementAttribute("{$parent}/w:vAlign", 'w:val')); + self::assertEquals($cStyles['textDirection'], $doc->getElementAttribute("{$parent}/w:textDirection", 'w:val')); + } + + /** + * covers ::_writeCellStyle. + */ + public function testWriteCellStyleCellGridSpan(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $table = $section->addTable(); + + $table->addRow(); + $cell = $table->addCell(200); + $cell->getStyle()->setGridSpan(5); + + $table->addRow(); + $table->addCell(40); + $table->addCell(40); + $table->addCell(40); + $table->addCell(40); + $table->addCell(40); + + $table->addRow(); + $cell = $table->addCell(200, ['borderRightColor' => 'FF0000']); + $cell->getStyle()->setGridSpan(5); + + $doc = TestHelperDOCX::getDocument($phpWord); + $element = $doc->getElement('/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:gridSpan'); + + self::assertEquals(5, $element->getAttribute('w:val')); + } + + /** + * Test write gutter and line numbering. + */ + public function testWriteGutterAndLineNumbering(): void + { + $pageMarginPath = '/w:document/w:body/w:sectPr/w:pgMar'; + $lineNumberingPath = '/w:document/w:body/w:sectPr/w:lnNumType'; + + $phpWord = new PhpWord(); + $phpWord->addSection(['gutter' => 240, 'lineNumbering' => []]); + $doc = TestHelperDOCX::getDocument($phpWord); + + self::assertEquals(240, $doc->getElement($pageMarginPath)->getAttribute('w:gutter')); + self::assertTrue($doc->elementExists($lineNumberingPath)); + } +} diff --git a/tests/PhpWord/Tests/Writer/Word2007/Part/FooterTest.php b/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php similarity index 62% rename from tests/PhpWord/Tests/Writer/Word2007/Part/FooterTest.php rename to tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php index 5c9d2d3081..beb6e971e2 100644 --- a/tests/PhpWord/Tests/Writer/Word2007/Part/FooterTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php @@ -1,4 +1,5 @@ addText(''); $container->addPreserveText(''); @@ -42,12 +47,17 @@ public function testWriteFooter() $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()); - $this->assertInstanceOf('SimpleXMLElement', $xml); + self::assertInstanceOf('SimpleXMLElement', $xml); + TestHelperDOCX::deleteDir($dir); } } diff --git a/tests/PhpWord/Tests/Writer/Word2007/Part/FootnotesTest.php b/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php similarity index 51% rename from tests/PhpWord/Tests/Writer/Word2007/Part/FootnotesTest.php rename to tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php index d7a3d667cf..d8bf678541 100644 --- a/tests/PhpWord/Tests/Writer/Word2007/Part/FootnotesTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php @@ -1,4 +1,5 @@ addParagraphStyle('pStyle', array('align' => 'left')); + $phpWord->addParagraphStyle('pStyle', ['alignment' => Jc::START]); $section = $phpWord->addSection(); $section->addText('Text'); $footnote1 = $section->addFootnote('pStyle'); $footnote1->addText('Footnote'); $footnote1->addTextBreak(); - $footnote1->addLink('http://google.com/'); - $footnote2 = $section->addEndnote(array('align' => 'left')); + $footnote1->addLink('https://github.com/PHPOffice/PHPWord'); + $footnote2 = $section->addEndnote(['alignment' => Jc::START]); $footnote2->addText('Endnote'); $doc = TestHelperDOCX::getDocument($phpWord); - $this->assertTrue($doc->elementExists("/w:document/w:body/w:p/w:r/w:footnoteReference")); - $this->assertTrue($doc->elementExists("/w:document/w:body/w:p/w:r/w:endnoteReference")); + 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/PhpWord/Tests/Writer/Word2007/Part/HeaderTest.php b/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php similarity index 62% rename from tests/PhpWord/Tests/Writer/Word2007/Part/HeaderTest.php rename to tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php index e8c31ecfb0..164365f113 100644 --- a/tests/PhpWord/Tests/Writer/Word2007/Part/HeaderTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php @@ -1,4 +1,5 @@ addText('Test'); @@ -43,12 +47,17 @@ public function testWriteHeader() $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()); - $this->assertInstanceOf('SimpleXMLElement', $xml); + self::assertInstanceOf('SimpleXMLElement', $xml); + TestHelperDOCX::deleteDir($dir); } } diff --git a/tests/PhpWord/Tests/Writer/Word2007/Part/NumberingTest.php b/tests/PhpWordTests/Writer/Word2007/Part/NumberingTest.php similarity index 66% rename from tests/PhpWord/Tests/Writer/Word2007/Part/NumberingTest.php rename to tests/PhpWordTests/Writer/Word2007/Part/NumberingTest.php index ebc3aa2e88..1cf596fc85 100644 --- a/tests/PhpWord/Tests/Writer/Word2007/Part/NumberingTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/NumberingTest.php @@ -1,4 +1,5 @@ addNumberingStyle( 'numStyle', - array( + [ 'type' => 'multilevel', - 'levels' => array( - array( + 'levels' => [ + [ 'start' => 1, - 'format' => 'decimal', + 'format' => NumberFormat::DECIMAL, 'restart' => 1, 'suffix' => 'space', 'text' => '%1.', - 'align' => 'left', + 'alignment' => Jc::START, 'left' => 360, 'hanging' => 360, 'tabPos' => 360, 'font' => 'Arial', 'hint' => 'default', - ), - ) - ) + ], + ], + ] ); $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); - $this->assertTrue($doc->elementExists('/w:numbering/w:abstractNum', $xmlFile)); + self::assertTrue($doc->elementExists('/w:numbering/w:abstractNum', $xmlFile)); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Part/SettingsTest.php b/tests/PhpWordTests/Writer/Word2007/Part/SettingsTest.php new file mode 100644 index 0000000000..107b94b1be --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Part/SettingsTest.php @@ -0,0 +1,488 @@ +getSettings()->getDocumentProtection()->setEditing('forms'); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:documentProtection'; + self::assertTrue($doc->elementExists($path, $file)); + } + + /** + * Test document protection with password. + */ + public function testDocumentProtectionWithPassword(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->getDocumentProtection()->setEditing('readOnly'); + $phpWord->getSettings()->getDocumentProtection()->setPassword('testÄö@€!$&'); + $phpWord->getSettings()->getDocumentProtection()->setSalt(base64_decode('uq81pJRRGFIY5U+E9gt8tA==')); + $phpWord->getSettings()->getDocumentProtection()->setAlgorithm(PasswordEncoder::ALGORITHM_MD2); + $phpWord->getSettings()->getDocumentProtection()->setSpinCount(10); + $sect = $phpWord->addSection(); + $sect->addText('This is a protected document'); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:documentProtection'; + self::assertTrue($doc->elementExists($path, $file)); + self::assertEquals('rUuJbk6LuN2/qFyp7IUPQA==', $doc->getElement($path, $file)->getAttribute('w:hash')); + self::assertEquals('1', $doc->getElement($path, $file)->getAttribute('w:cryptAlgorithmSid')); + self::assertEquals('10', $doc->getElement($path, $file)->getAttribute('w:cryptSpinCount')); + } + + /** + * Test document protection with password without setting salt. + */ + public function testDocumentProtectionWithPasswordNoSalt(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->getDocumentProtection()->setEditing('readOnly'); + $phpWord->getSettings()->getDocumentProtection()->setPassword('testÄö@€!$&'); + //$phpWord->getSettings()->getDocumentProtection()->setSalt(base64_decode('uq81pJRRGFIY5U+E9gt8tA==')); + $phpWord->getSettings()->getDocumentProtection()->setAlgorithm(PasswordEncoder::ALGORITHM_MD2); + $phpWord->getSettings()->getDocumentProtection()->setSpinCount(10); + $sect = $phpWord->addSection(); + $sect->addText('This is a protected document'); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:documentProtection'; + self::assertTrue($doc->elementExists($path, $file)); + //$this->assertEquals('rUuJbk6LuN2/qFyp7IUPQA==', $doc->getElement($path, $file)->getAttribute('w:hash')); + self::assertEquals('1', $doc->getElement($path, $file)->getAttribute('w:cryptAlgorithmSid')); + self::assertEquals('10', $doc->getElement($path, $file)->getAttribute('w:cryptSpinCount')); + } + + /** + * Test compatibility. + */ + public function testCompatibility(): void + { + $phpWord = new PhpWord(); + $phpWord->getCompatibility()->setOoxmlVersion(15); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:compat/w:compatSetting'; + self::assertTrue($doc->elementExists($path, $file)); + self::assertEquals($phpWord->getCompatibility()->getOoxmlVersion(), 15); + } + + /** + * Test language. + */ + public function testDefaultLanguage(): void + { + $phpWord = new PhpWord(); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:themeFontLang'; + self::assertTrue($doc->elementExists($path, $file)); + $element = $doc->getElement($path, $file); + + self::assertEquals('en-US', $element->getAttribute('w:val')); + } + + /** + * Test language. + */ + public function testLanguage(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setThemeFontLang(new Language(Language::DE_DE, Language::KO_KR, Language::HE_IL)); + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:themeFontLang'; + self::assertTrue($doc->elementExists($path, $file)); + $element = $doc->getElement($path, $file); + + self::assertEquals(Language::DE_DE, $element->getAttribute('w:val')); + self::assertEquals(Language::KO_KR, $element->getAttribute('w:eastAsia')); + self::assertEquals(Language::HE_IL, $element->getAttribute('w:bidi')); + } + + /** + * Test proofState. + */ + public function testProofState(): void + { + $proofState = new ProofState(); + $proofState->setSpelling(ProofState::DIRTY); + $proofState->setGrammar(ProofState::DIRTY); + $phpWord = new PhpWord(); + $phpWord->getSettings()->setProofState($proofState); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:proofState'; + self::assertTrue($doc->elementExists($path, $file)); + $element = $doc->getElement($path, $file); + + self::assertEquals('dirty', $element->getAttribute('w:spelling')); + self::assertEquals('dirty', $element->getAttribute('w:grammar')); + } + + /** + * Test spelling. + */ + public function testSpelling(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setHideSpellingErrors(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:hideSpellingErrors'; + self::assertTrue($doc->elementExists($path, $file)); + $element = $doc->getElement($path, $file); + + self::assertSame('true', $element->getAttribute('w:val')); + } + + /** + * Test even and odd headers. + */ + public function testEvenAndOddHeaders(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setEvenAndOddHeaders(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:evenAndOddHeaders'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('true', $element->getAttribute('w:val')); + } + + public function testUpdateFields(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setUpdateFields(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:updateFields'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('true', $element->getAttribute('w:val')); + } + + /** + * Test zoom percentage. + */ + public function testZoomPercentage(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setZoom(75); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:zoom'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertEquals('75', $element->getAttribute('w:percent')); + } + + /** + * Test zoom value. + */ + public function testZoomValue(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setZoom(Zoom::FULL_PAGE); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:zoom'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertEquals('fullPage', $element->getAttribute('w:val')); + } + + public function testMirrorMargins(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setMirrorMargins(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:mirrorMargins'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('true', $element->getAttribute('w:val')); + } + + /** + * Test Revision View. + */ + public function testRevisionView(): void + { + $trackChangesView = new TrackChangesView(); + $trackChangesView->setFormatting(false); + $trackChangesView->setComments(true); + + $phpWord = new PhpWord(); + $phpWord->getSettings()->setRevisionView($trackChangesView); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:revisionView'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertEquals('false', $element->getAttribute('w:formatting')); + self::assertEquals('true', $element->getAttribute('w:comments')); + } + + public function testHideSpellingErrors(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setHideSpellingErrors(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:hideSpellingErrors'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('true', $element->getAttribute('w:val')); + } + + public function testHideGrammaticalErrors(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setHideGrammaticalErrors(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:hideGrammaticalErrors'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('true', $element->getAttribute('w:val')); + } + + /** + * Test track Revisions. + */ + public function testTrackRevisions(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setTrackRevisions(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:trackRevisions'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('true', $element->getAttribute('w:val')); + } + + /** + * Test doNotTrackMoves. + */ + public function testDoNotTrackMoves(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setDoNotTrackMoves(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:doNotTrackMoves'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('true', $element->getAttribute('w:val')); + } + + /** + * Test DoNotTrackFormatting. + */ + public function testDoNotTrackFormatting(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setDoNotTrackFormatting(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:doNotTrackFormatting'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('true', $element->getAttribute('w:val')); + } + + public function testAutoHyphenation(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setAutoHyphenation(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:autoHyphenation'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('true', $element->getAttribute('w:val')); + } + + public function testConsecutiveHyphenLimit(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setConsecutiveHyphenLimit(2); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:consecutiveHyphenLimit'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('2', $element->getAttribute('w:val')); + } + + public function testHyphenationZone(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setHyphenationZone(100); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:hyphenationZone'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('100', $element->getAttribute('w:val')); + } + + public function testDoNotHyphenateCaps(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setDoNotHyphenateCaps(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:doNotHyphenateCaps'; + self::assertTrue($doc->elementExists($path, $file)); + + $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/Part/StylesTest.php b/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php new file mode 100644 index 0000000000..09936d6d33 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php @@ -0,0 +1,186 @@ + Jc::BOTH]; + $pBase = ['basedOn' => 'Normal']; + $pNew = ['basedOn' => 'Base Style', 'next' => 'Normal']; + $rStyle = ['size' => 20]; + $tStyle = ['bgColor' => 'FF0000', 'cellMargin' => 120, 'borderSize' => 120]; + $firstRowStyle = ['bgColor' => '0000FF', 'borderSize' => 120, 'borderColor' => '00FF00']; + $phpWord->setDefaultParagraphStyle($pStyle); + $phpWord->addParagraphStyle('Base Style', $pBase); + $phpWord->addParagraphStyle('New Style', $pNew); + $phpWord->addFontStyle('New Style', $rStyle, $pStyle); + $phpWord->addTableStyle('Table Style', $tStyle, $firstRowStyle); + $phpWord->addTitleStyle(1, $rStyle, $pStyle); + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/styles.xml'; + + // Normal style generated? + $path = '/w:styles/w:style[@w:styleId="Normal"]/w:name'; + $element = $doc->getElement($path, $file); + self::assertEquals('Normal', $element->getAttribute('w:val')); + + // Parent style referenced? + $path = '/w:styles/w:style[@w:styleId="New Style"]/w:basedOn'; + $element = $doc->getElement($path, $file); + self::assertEquals('Base Style', $element->getAttribute('w:val')); + + // Next paragraph style correct? + $path = '/w:styles/w:style[@w:styleId="New Style"]/w:next'; + $element = $doc->getElement($path, $file); + self::assertEquals('Normal', $element->getAttribute('w:val')); + } + + public function testFontStyleBasedOn(): void + { + $phpWord = new PhpWord(); + + $baseParagraphStyle = new Paragraph(); + $baseParagraphStyle->setAlignment(Jc::CENTER); + $baseParagraphStyle = $phpWord->addParagraphStyle('BaseStyle', $baseParagraphStyle); + + $childFont = new Font(); + $childFont->setParagraph($baseParagraphStyle); + $childFont->setSize(16); + $childFont = $phpWord->addFontStyle('ChildFontStyle', $childFont); + + $otherFont = new Font(); + $otherFont->setSize(20); + $otherFont = $phpWord->addFontStyle('OtherFontStyle', $otherFont); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/styles.xml'; + + // Normal style generated? + $path = '/w:styles/w:style[@w:styleId="BaseStyle"]/w:name'; + $element = $doc->getElement($path, $file); + self::assertEquals('BaseStyle', $element->getAttribute('w:val')); + + // Font style with paragraph should have it's base style set to that paragraphs style name + $path = '/w:styles/w:style[w:name/@w:val="ChildFontStyle"]/w:basedOn'; + $element = $doc->getElement($path, $file); + self::assertEquals('BaseStyle', $element->getAttribute('w:val')); + + // Font style without paragraph should not have a base style set + $path = '/w:styles/w:style[w:name/@w:val="OtherFontStyle"]/w:basedOn'; + $element = $doc->getElement($path, $file); + self::assertNull($element); + } + + public function testFontStyleBasedOnOtherFontStyle(): void + { + $phpWord = new PhpWord(); + + $styleGenerationP = new Paragraph(); + $styleGenerationP->setAlignment(Jc::BOTH); + + $styleGeneration = new Font(); + $styleGeneration->setParagraph($styleGenerationP); + $styleGeneration->setSize(9.5); + $phpWord->addFontStyle('Generation', $styleGeneration); + + $styleGenerationEteinteP = new Paragraph(); + $styleGenerationEteinteP->setBasedOn('Generation'); + + $styleGenerationEteinte = new Font(); + $styleGenerationEteinte->setParagraph($styleGenerationEteinteP); + $styleGenerationEteinte->setSize(8.5); + $phpWord->addFontStyle('GeneratEteinte', $styleGenerationEteinte); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/styles.xml'; + + $path = '/w:styles/w:style[@w:styleId="GeneratEteinte"]/w:basedOn'; + $element = $doc->getElement($path, $file); + self::assertEquals('Generation', $element->getAttribute('w:val')); + } + + /** + * Test default font color. + */ + public function testDefaultDefaultFontColor(): void + { + $phpWord = new PhpWord(); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/styles.xml'; + + $path = '/w:styles/w:docDefaults/w:rPrDefault/w:rPr/w:color'; + self::assertTrue($doc->elementExists($path, $file)); + $element = $doc->getElement($path, $file); + + self::assertEquals('000000', $element->getAttribute('w:val')); + } + + /** + * Test default font color. + */ + public function testDefaultFontColor(): void + { + $phpWord = new PhpWord(); + $defaultFontColor = '00FF00'; + $phpWord->setDefaultFontColor($defaultFontColor); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/styles.xml'; + + $path = '/w:styles/w:docDefaults/w:rPrDefault/w:rPr/w:color'; + self::assertTrue($doc->elementExists($path, $file)); + $element = $doc->getElement($path, $file); + + self::assertEquals($defaultFontColor, $element->getAttribute('w:val')); + } +} diff --git a/tests/PhpWord/Tests/Writer/Word2007/PartTest.php b/tests/PhpWordTests/Writer/Word2007/PartTest.php similarity index 68% rename from tests/PhpWord/Tests/Writer/Word2007/PartTest.php rename to tests/PhpWordTests/Writer/Word2007/PartTest.php index c3d2933190..be69e587e3 100644 --- a/tests/PhpWord/Tests/Writer/Word2007/PartTest.php +++ b/tests/PhpWordTests/Writer/Word2007/PartTest.php @@ -1,4 +1,5 @@ expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectExceptionMessage('Invalid parameters passed.'); $object = new RelsPart(); - $object->setMedia(array(array('type' => '', 'target' => ''))); + $object->setMedia([['type' => '', 'target' => '']]); $object->write(); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Style/DirectionTest.php b/tests/PhpWordTests/Writer/Word2007/Style/DirectionTest.php new file mode 100644 index 0000000000..efd855af23 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Style/DirectionTest.php @@ -0,0 +1,62 @@ +addSection(); + $html = '

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

    '; + SharedHtml::addHtml($section, $html, false, false); + $english = '

    LTR in RTL document.

    '; + 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 new file mode 100644 index 0000000000..c0e2653a7c --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php @@ -0,0 +1,201 @@ +addSection(); + $textrun = $section->addTextRun(); + $textrun->addText('سلام این یک پاراگراف راست به چپ است', ['rtl' => true, 'lang' => 'ar-DZ']); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $file = 'word/document.xml'; + $path = '/w:document/w:body/w:p/w:r/w:rPr/w:rtl'; + self::assertTrue($doc->elementExists($path, $file)); + } + + public function testFontRTLNamed(): void + { + $phpWord = new PhpWord(); + $stnam = 'fstyle'; + $phpWord->addFontStyle($stnam, [ + 'rtl' => true, + 'name' => 'Courier New', + 'size' => 8, + ]); + $section = $phpWord->addSection(); + $txt = 'היום יום שני'; // Translation = Today is Monday + $section->addText($txt, $stnam); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $element = '/w:document/w:body/w:p/w:r'; + $txtelem = $element . '/w:t'; + $styelem = $element . '/w:rPr'; + self::assertTrue($doc->elementExists($txtelem)); + self::assertEquals($txt, $doc->getElement($txtelem)->textContent); + self::assertTrue($doc->elementExists($styelem)); + self::assertTrue($doc->elementExists($styelem . '/w:rStyle')); + self::assertEquals($stnam, $doc->getElementAttribute($styelem . '/w:rStyle', 'w:val')); + self::assertTrue($doc->elementExists($styelem . '/w:rtl')); + } + + public function testFontNotRTLNamed(): void + { + $phpWord = new PhpWord(); + $stnam = 'fstyle'; + $phpWord->addFontStyle($stnam, [ + //'rtl' => true, + 'name' => 'Courier New', + 'size' => 8, + ]); + $section = $phpWord->addSection(); + $txt = 'היום יום שני'; // Translation = Today is Monday + $section->addText($txt, $stnam); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $element = '/w:document/w:body/w:p/w:r'; + $txtelem = $element . '/w:t'; + $styelem = $element . '/w:rPr'; + self::assertTrue($doc->elementExists($txtelem)); + self::assertEquals($txt, $doc->getElement($txtelem)->textContent); + self::assertTrue($doc->elementExists($styelem)); + self::assertTrue($doc->elementExists($styelem . '/w:rStyle')); + self::assertEquals($stnam, $doc->getElementAttribute($styelem . '/w:rStyle', 'w:val')); + self::assertFalse($doc->elementExists($styelem . '/w:rtl')); + } + + public function testNoProof(): void + { + $phpWord = new PhpWord(); + $fontStyle = [ + 'noProof' => true, + 'name' => 'Courier New', + 'size' => 8, + ]; + $section = $phpWord->addSection(); + $txt = 'spellung error'; + $section->addText($txt, $fontStyle); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $element = '/w:document/w:body/w:p/w:r'; + $txtelem = $element . '/w:t'; + $styelem = $element . '/w:rPr'; + $noproofelem = $styelem . '/w:noProof'; + self::assertTrue($doc->elementExists($txtelem)); + self::assertEquals($txt, $doc->getElement($txtelem)->textContent); + self::assertTrue($doc->elementExists($styelem)); + self::assertTrue($doc->elementExists($noproofelem)); + self::assertEquals('1', $doc->getElementAttribute($noproofelem, 'w:val')); + } + + /** + * Test writing font with language. + */ + public function testFontWithLang(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Ce texte-ci est en français.', ['lang' => \PhpOffice\PhpWord\Style\Language::FR_BE]); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $file = 'word/document.xml'; + $path = '/w:document/w:body/w:p/w:r/w:rPr/w:lang'; + self::assertTrue($doc->elementExists($path, $file)); + } + + /** + * Test writing position. + */ + public function testPosition(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('This text is lowered', ['position' => -20]); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:p/w:r/w:rPr/w:position'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals(-20, $doc->getElementAttribute($path, 'w:val')); + } + + public static function testRgb(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(['pageNumberingStart' => 1]); + $html = implode( + "\n", + [ + '', + '', + '', + '', + '', + '', + '', + '
    This one is in color.This one too.
    ', + ] + ); + + Html::addHtml($section, $html, false, false); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $element = '/w:document/w:body/w:tbl/w:tr/w:tc/w:p/w:r'; + $txtelem = $element . '/w:t'; + $styelem = $element . '/w:rPr'; + self::assertTrue($doc->elementExists($txtelem)); + self::assertSame('This one is in color.', $doc->getElement($txtelem)->textContent); + self::assertTrue($doc->elementExists($styelem)); + self::assertTrue($doc->elementExists($styelem . '/w:color')); + self::assertSame('A7D9C1', $doc->getElementAttribute($styelem . '/w:color', 'w:val')); + + $element = '/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:p/w:r'; + $txtelem = $element . '/w:t'; + $styelem = $element . '/w:rPr'; + self::assertTrue($doc->elementExists($txtelem)); + self::assertSame('This one too.', $doc->getElement($txtelem)->textContent); + self::assertTrue($doc->elementExists($styelem)); + self::assertTrue($doc->elementExists($styelem . '/w:color')); + self::assertSame('A7D9C1', $doc->getElementAttribute($styelem . '/w:color', 'w:val')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Style/ImageTest.php b/tests/PhpWordTests/Writer/Word2007/Style/ImageTest.php new file mode 100644 index 0000000000..2b5c25e01f --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Style/ImageTest.php @@ -0,0 +1,107 @@ + Image::WRAP_INLINE, + 'wrapDistanceLeft' => 10, + 'wrapDistanceRight' => 20, + 'wrapDistanceTop' => 30, + 'wrapDistanceBottom' => 40, + ]; + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $section->addImage(__DIR__ . '/../../../_files/images/earth.jpg', $styles); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:p[1]/w:r/w:rPr/w:position'; + self::assertFalse($doc->elementExists($path)); + $path = '/w:document/w:body/w:p[1]/w:r/w:pict/v:shape'; + self::assertTrue($doc->elementExists($path . '/w10:wrap')); + self::assertEquals('inline', $doc->getElementAttribute($path . '/w10:wrap', 'type')); + + self::assertTrue($doc->elementExists($path)); + $style = $doc->getElement($path)->getAttribute('style'); + self::assertNotNull($style); + self::assertStringContainsString('mso-wrap-distance-left:10pt;', $style); + self::assertStringContainsString('mso-wrap-distance-right:20pt;', $style); + self::assertStringContainsString('mso-wrap-distance-top:30pt;', $style); + self::assertStringContainsString('mso-wrap-distance-bottom:40pt;', $style); + } + + /** + * Test writing image wrapping. + */ + public function testWrappingWithPosition(): void + { + $styles = [ + 'wrap' => Image::WRAP_INLINE, + 'wrapDistanceLeft' => 10, + 'wrapDistanceRight' => 20, + 'wrapDistanceTop' => 30, + 'wrapDistanceBottom' => 40, + 'position' => 10, + ]; + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $section->addImage(__DIR__ . '/../../../_files/images/earth.jpg', $styles); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:p[1]/w:r/w:rPr/w:position'; + self::assertEquals('10', $doc->getElement($path)->getAttribute('w:val')); + $path = '/w:document/w:body/w:p[1]/w:r/w:pict/v:shape'; + self::assertTrue($doc->elementExists($path . '/w10:wrap')); + self::assertEquals('inline', $doc->getElementAttribute($path . '/w10:wrap', 'type')); + + self::assertTrue($doc->elementExists($path)); + $style = $doc->getElement($path)->getAttribute('style'); + self::assertNotNull($style); + self::assertStringContainsString('mso-wrap-distance-left:10pt;', $style); + self::assertStringContainsString('mso-wrap-distance-right:20pt;', $style); + self::assertStringContainsString('mso-wrap-distance-top:30pt;', $style); + self::assertStringContainsString('mso-wrap-distance-bottom:40pt;', $style); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Style/IndentationTest.php b/tests/PhpWordTests/Writer/Word2007/Style/IndentationTest.php new file mode 100644 index 0000000000..44d071415a --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Style/IndentationTest.php @@ -0,0 +1,71 @@ +addSection(); + $text = $section->addText('AA'); + $paragraphStyle = $text->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + $paragraphStyle->setIndentation([]); + $doc = TestHelperDOCX::getDocument($word, 'Word2007'); + + $path = '/w:document/w:body/w:p[1]/w:pPr/w:ind'; + self::assertTrue($doc->elementExists($path)); + self::assertFalse($doc->hasElementAttribute($path, 'w:firstLineChars')); + } + + public function testFirstLineChars(): void + { + $word = new PhpWord(); + Settings::setDefaultRtl(true); + $section = $word->addSection(); + $text = $section->addText('AA'); + $paragraphStyle = $text->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + $paragraphStyle->setIndentation([ + 'firstLineChars' => 1440, + ]); + $doc = TestHelperDOCX::getDocument($word, 'Word2007'); + + $path = '/w:document/w:body/w:p[1]/w:pPr/w:ind'; + self::assertTrue($doc->elementExists($path)); + self::assertTrue($doc->hasElementAttribute($path, 'w:firstLineChars')); + self::assertSame('1440', $doc->getElementAttribute($path, 'w:firstLineChars')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Style/ParagraphTest.php b/tests/PhpWordTests/Writer/Word2007/Style/ParagraphTest.php new file mode 100644 index 0000000000..c1f985ccd2 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Style/ParagraphTest.php @@ -0,0 +1,95 @@ +addParagraphStyle('testStyle', ['indent' => '10']); + $section = $phpWord->addSection(); + $section->addText('test', null, ['numStyle' => 'testStyle', 'numLevel' => '1']); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:p/w:pPr/w:numPr/w:ilvl'; + self::assertTrue($doc->elementExists($path)); + } + + public function testLineSpacingExact(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $section->addText('test', null, ['spacing' => 240, 'spacingLineRule' => 'exact']); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:p/w:pPr/w:spacing'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals('exact', $doc->getElementAttribute($path, 'w:lineRule')); + self::assertEquals('240', $doc->getElementAttribute($path, 'w:line')); + } + + public function testLineSpacingAuto(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $section->addText('test', null, ['spacing' => 240, 'spacingLineRule' => 'auto']); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:p/w:pPr/w:spacing'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals('auto', $doc->getElementAttribute($path, 'w:lineRule')); + self::assertEquals('480', $doc->getElementAttribute($path, 'w:line')); + } + + public function testSuppressAutoHyphens(): void + { + $paragraphStyle = new ParagraphStyle(); + $paragraphStyle->setSuppressAutoHyphens(true); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $section->addText('test', null, $paragraphStyle); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:p/w:pPr/w:suppressAutoHyphens'; + self::assertTrue($doc->elementExists($path)); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Style/SectionTest.php b/tests/PhpWordTests/Writer/Word2007/Style/SectionTest.php new file mode 100644 index 0000000000..f527ded190 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Style/SectionTest.php @@ -0,0 +1,59 @@ +addSection(); + $section->getStyle()->setMarginTop(0.1)->setMarginBottom(0.4)->setMarginLeft(0.2)->setMarginRight(0.3); + $section->addText('test'); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + Settings::setMeasurementUnit($unit); + + $path = '/w:document/w:body/w:sectPr/w:pgMar'; + self::assertEquals('144', $doc->getElementAttribute($path, 'w:top')); + self::assertEquals('432', $doc->getElementAttribute($path, 'w:right')); + self::assertEquals('576', $doc->getElementAttribute($path, 'w:bottom')); + self::assertEquals('288', $doc->getElementAttribute($path, 'w:left')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Style/TableCellTest.php b/tests/PhpWordTests/Writer/Word2007/Style/TableCellTest.php new file mode 100644 index 0000000000..bd3f587b75 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Style/TableCellTest.php @@ -0,0 +1,86 @@ +addSection(); + $table = $section->addTable(); + $table->addRow(); + + $testValTop = Converter::pixelToTwip(10); + $testValRight = Converter::pixelToTwip(11); + $testValBottom = Converter::pixelToTwip(12); + $testValLeft = Converter::pixelToTwip(13); + + $cellStyle = [ + 'paddingTop' => $testValTop, + 'paddingRight' => $testValRight, + 'paddingBottom' => $testValBottom, + 'paddingLeft' => $testValLeft, + ]; + $table->addCell(null, $cellStyle)->addText('Some text'); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:tcMar/w:top'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals($testValTop, $doc->getElementAttribute($path, 'w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type')); + + $path = '/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:tcMar/w:start'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals($testValLeft, $doc->getElementAttribute($path, 'w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type')); + + $path = '/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:tcMar/w:bottom'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals($testValBottom, $doc->getElementAttribute($path, 'w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type')); + + $path = '/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:tcMar/w:end'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals($testValRight, $doc->getElementAttribute($path, 'w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Style/TableTest.php b/tests/PhpWordTests/Writer/Word2007/Style/TableTest.php new file mode 100644 index 0000000000..b10d836568 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Style/TableTest.php @@ -0,0 +1,163 @@ +setLayout(Table::LAYOUT_FIXED); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $table = $section->addTable($tableStyle); + $table->addRow(); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:tbl/w:tblPr/w:tblLayout'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals(Table::LAYOUT_FIXED, $doc->getElementAttribute($path, 'w:type')); + } + + /** + * Test write styles. + */ + public function testCellSpacing(): void + { + $tableStyle = new Table(); + $tableStyle->setCellSpacing(10.3); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $table = $section->addTable($tableStyle); + $table->addRow(); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:tbl/w:tblPr/w:tblCellSpacing'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals(10.3, $doc->getElementAttribute($path, 'w:w')); + self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type')); + } + + /** + * Test write table position. + */ + public function testTablePosition(): void + { + $tablePosition = [ + 'leftFromText' => 10, + 'rightFromText' => 20, + 'topFromText' => 30, + 'bottomFromText' => 40, + 'vertAnchor' => TablePosition::VANCHOR_PAGE, + 'horzAnchor' => TablePosition::HANCHOR_MARGIN, + 'tblpXSpec' => TablePosition::XALIGN_CENTER, + 'tblpX' => 50, + 'tblpYSpec' => TablePosition::YALIGN_TOP, + 'tblpY' => 60, + ]; + $tableStyle = new Table(); + $tableStyle->setPosition($tablePosition); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $table = $section->addTable($tableStyle); + $table->addRow(); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:tbl/w:tblPr/w:tblpPr'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals(10, $doc->getElementAttribute($path, 'w:leftFromText')); + self::assertEquals(20, $doc->getElementAttribute($path, 'w:rightFromText')); + self::assertEquals(30, $doc->getElementAttribute($path, 'w:topFromText')); + self::assertEquals(40, $doc->getElementAttribute($path, 'w:bottomFromText')); + self::assertEquals(TablePosition::VANCHOR_PAGE, $doc->getElementAttribute($path, 'w:vertAnchor')); + self::assertEquals(TablePosition::HANCHOR_MARGIN, $doc->getElementAttribute($path, 'w:horzAnchor')); + self::assertEquals(TablePosition::XALIGN_CENTER, $doc->getElementAttribute($path, 'w:tblpXSpec')); + self::assertEquals(50, $doc->getElementAttribute($path, 'w:tblpX')); + self::assertEquals(TablePosition::YALIGN_TOP, $doc->getElementAttribute($path, 'w:tblpYSpec')); + self::assertEquals(60, $doc->getElementAttribute($path, 'w:tblpY')); + } + + public function testIndent(): void + { + $value = 100; + $type = TblWidth::TWIP; + + $tableStyle = new Table(); + $tableStyle->setIndent(new TblWidthComplexType($value, $type)); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $table = $section->addTable($tableStyle); + $table->addRow(); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:tbl/w:tblPr/w:tblInd'; + self::assertTrue($doc->elementExists($path)); + self::assertSame($value, (int) $doc->getElementAttribute($path, 'w:w')); + self::assertSame($type, $doc->getElementAttribute($path, 'w:type')); + } + + public function testRigthToLeft(): void + { + $tableStyle = new Table(); + $tableStyle->setBidiVisual(true); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $table = $section->addTable($tableStyle); + $table->addRow(); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:tbl/w:tblPr/w:bidiVisual'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals('1', $doc->getElementAttribute($path, 'w:val')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/StyleTest.php b/tests/PhpWordTests/Writer/Word2007/StyleTest.php new file mode 100644 index 0000000000..0c0d0b854a --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/StyleTest.php @@ -0,0 +1,86 @@ +write(); + + self::assertEquals('', $xmlWriter->getData()); + } + } + + /** + * Test method exceptions. + */ + public function testMethodExceptionsFrame(): void + { + $xmlWriter = new XMLWriter(); + $object = new Frame($xmlWriter); + $object->writeAlignment(); + + self::assertEquals('', $xmlWriter->getData()); + } + + /** + * Test method exceptions. + */ + public function testMethodExceptionsLine(): void + { + $xmlWriter = new XMLWriter(); + $object = new Line($xmlWriter); + $object->writeStroke(); + + self::assertEquals('', $xmlWriter->getData()); + } + + /** + * Test method exceptions. + */ + public function testMethodExceptionsTextBox(): void + { + $xmlWriter = new XMLWriter(); + $object = new TextBox($xmlWriter); + $object->writeBorder(); + + self::assertEquals('', $xmlWriter->getData()); + } +} diff --git a/tests/PhpWord/Tests/Writer/Word2007Test.php b/tests/PhpWordTests/Writer/Word2007Test.php similarity index 54% rename from tests/PhpWord/Tests/Writer/Word2007Test.php rename to tests/PhpWordTests/Writer/Word2007Test.php index db6e5cadd2..64f6a29b52 100644 --- a/tests/PhpWord/Tests/Writer/Word2007Test.php +++ b/tests/PhpWordTests/Writer/Word2007Test.php @@ -1,4 +1,5 @@ 'ContentTypes', 'Rels' => 'Rels', 'DocPropsApp' => 'DocPropsApp', @@ -55,13 +61,13 @@ public function testConstruct() 'Footer' => 'Footer', 'Footnotes' => 'Footnotes', 'Endnotes' => 'Footnotes', - ); + ]; foreach ($writerParts as $part => $type) { - $this->assertInstanceOf( + self::assertInstanceOf( "PhpOffice\\PhpWord\\Writer\\Word2007\\Part\\{$type}", $object->getWriterPart($part) ); - $this->assertInstanceOf( + self::assertInstanceOf( 'PhpOffice\\PhpWord\\Writer\\Word2007', $object->getWriterPart($part)->getParentWriter() ); @@ -69,15 +75,15 @@ public function testConstruct() } /** - * Save + * Save. */ - public function testSave() + public function testSave(): void { $localImage = __DIR__ . '/../_files/images/earth.jpg'; - $remoteImage = 'http://php.net//images/logos/php-med-trans-light.gif'; + $remoteImage = self::getRemoteGifImageUrl(); $phpWord = new PhpWord(); - $phpWord->addFontStyle('Font', array('size' => 11)); - $phpWord->addParagraphStyle('Paragraph', array('align' => 'center')); + $phpWord->addFontStyle('Font', ['size' => 11]); + $phpWord->addParagraphStyle('Paragraph', ['alignment' => Jc::CENTER]); $section = $phpWord->addSection(); $section->addText('Test 1', 'Font', 'Paragraph'); $section->addTextBreak(); @@ -86,25 +92,25 @@ public function testSave() $textrun = $section->addTextRun(); $textrun->addText('Test 3'); $footnote = $textrun->addFootnote(); - $footnote->addLink('http://test.com/'); + $footnote->addLink('https://github.com/PHPOffice/PHPWord'); $header = $section->addHeader(); $header->addImage($localImage); $footer = $section->addFooter(); $footer->addImage($remoteImage); $writer = new Word2007($phpWord); - $file = __DIR__ . "/../_files/temp.docx"; + $file = __DIR__ . '/../_files/temp.docx'; $writer->save($file); - $this->assertTrue(file_exists($file)); + self::assertFileExists($file); unlink($file); } /** - * Save using disk caching + * Save using disk caching. */ - public function testSaveUseDiskCaching() + public function testSaveUseDiskCaching(): void { $phpWord = new PhpWord(); $section = $phpWord->addSection(); @@ -113,28 +119,33 @@ public function testSaveUseDiskCaching() $footnote->addText('Test'); $writer = new Word2007($phpWord); - $writer->setUseDiskCaching(true); - $file = __DIR__ . "/../_files/temp.docx"; + $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); - $this->assertTrue(file_exists($file)); + self::assertFileExists($file); unlink($file); + TestHelperDOCX::deleteDir($dir); } /** - * Check content types + * Check content types. */ - public function testCheckContentTypes() + public function testCheckContentTypes(): void { - $images = array( - 'mars_noext_jpg' => '1.jpg', - 'mars.jpg' => '2.jpg', - 'mario.gif' => '3.gif', - 'firefox.png' => '4.png', - 'duke_nukem.bmp' => '5.bmp', + $images = [ + 'mars_noext_jpg' => '1.jpg', + 'mars.jpg' => '2.jpg', + 'mario.gif' => '3.gif', + 'firefox.png' => '4.png', + 'duke_nukem.bmp' => '5.bmp', 'angela_merkel.tif' => '6.tif', - ); + ]; $phpWord = new PhpWord(); $section = $phpWord->addSection(); foreach ($images as $source => $target) { @@ -142,10 +153,10 @@ public function testCheckContentTypes() } $doc = TestHelperDOCX::getDocument($phpWord); - $mediaPath = $doc->getPath() . "/word/media"; + $mediaPath = $doc->getPath() . '/word/media'; foreach ($images as $source => $target) { - $this->assertFileEquals( + self::assertFileEquals( __DIR__ . "/../_files/images/{$source}", $mediaPath . "/section_image{$target}" ); @@ -153,42 +164,62 @@ public function testCheckContentTypes() } /** - * Get writer part return null value + * Get writer part return null value. */ - public function testGetWriterPartNull() + public function testGetWriterPartNull(): void { $object = new Word2007(); - $this->assertNull($object->getWriterPart()); + self::assertNull($object->getWriterPart()); } /** - * Set/get use disk caching + * Set/get use disk caching. */ - public function testSetGetUseDiskCaching() + public function testSetGetUseDiskCaching(): 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'); - - $this->assertTrue($object->isUseDiskCaching()); + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); + self::assertTrue($object->isUseDiskCaching()); + self::assertNotEmpty($contents); } /** - * Use disk caching exception - * - * @expectedException \PhpOffice\PhpWord\Exception\Exception + * Use disk caching exception. */ - public function testSetUseDiskCachingException() + public function testSetUseDiskCachingException(): void { - $dir = join( - DIRECTORY_SEPARATOR, - array(PHPWORD_TESTS_BASE_DIR, 'foo') - ); + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $dir = implode(DIRECTORY_SEPARATOR, [PHPWORD_TESTS_BASE_DIR, 'foo']); $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 new file mode 100644 index 0000000000..8c865932ba --- /dev/null +++ b/tests/PhpWordTests/XmlDocument.php @@ -0,0 +1,214 @@ +path = realpath($path); + } + + /** + * Get default file. + */ + public function getDefaultFile(): string + { + return $this->defaultFile; + } + + /** + * Set default file. + */ + public function setDefaultFile(string $file): string + { + $temp = $this->defaultFile; + + $this->defaultFile = $file; + + return $temp; + } + + /** + * Get file name. + */ + public function getFile(): string + { + return $this->file; + } + + /** + * Get path. + */ + public function getPath(): string + { + return $this->path; + } + + /** + * Get DOM from file. + */ + public function getFileDom(string $file = ''): DOMDocument + { + if (!$file) { + $file = $this->defaultFile; + } + if (null !== $this->dom && $file === $this->file) { + return $this->dom; + } + + $this->xpath = null; + $this->file = $file; + + $file = $this->path . '/' . $file; + if (\PHP_VERSION_ID < 80000) { + $orignalLibEntityLoader = libxml_disable_entity_loader(false); + } + $this->dom = new DOMDocument(); + $this->dom->load($file); + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($orignalLibEntityLoader); + } + + return $this->dom; + } + + /** + * Get node list. + * + * @return DOMNodeList + */ + public function getNodeList(string $path, string $file = ''): DOMNodeList + { + if (!$file) { + $file = $this->defaultFile; + } + if (null === $this->dom || $file !== $this->file) { + $this->getFileDom($file); + } + + if (null === $this->xpath) { + $this->xpath = new DOMXPath($this->dom); + $this->xpath->registerNamespace('w14', 'http://schemas.microsoft.com/office/word/2010/wordml'); + } + + return $this->xpath->query($path); + } + + /** + * Get element. + */ + public function getElement(string $path, string $file = ''): ?DOMElement + { + return $this->getNodeList($path, $file)->item(0); + } + + /** + * Get element attribute. + */ + public function getElementAttribute(string $path, string $attribute, string $file = ''): string + { + return $this->getElement($path, $file)->getAttribute($attribute); + } + + /** + * Return if element attribute exists. + */ + public function hasElementAttribute(string $path, string $attribute, string $file = ''): bool + { + return $this->getElement($path, $file)->hasAttribute($attribute); + } + + /** + * Check if element exists. + */ + public function elementExists(string $path, string $file = ''): bool + { + $nodeList = $this->getNodeList($path, $file); + + return $nodeList->length != 0; + } + + /** + * Returns the xml, or part of it as a formatted string. + * + * @return false|string + */ + public function printXml(string $path = '/', string $file = '') + { + $element = $this->getElement($path, $file); + + $newdoc = new DOMDocument(); + $newdoc->formatOutput = true; + $newdoc->preserveWhiteSpace = false; + $node = $newdoc->importNode($element, true); + $newdoc->appendChild($node); + + return $newdoc->saveXML($node); + } +} 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-2011.docx b/tests/PhpWordTests/_files/documents/reader-2011.docx new file mode 100644 index 0000000000..be94eca524 Binary files /dev/null and b/tests/PhpWordTests/_files/documents/reader-2011.docx 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.doc b/tests/PhpWordTests/_files/documents/reader.doc new file mode 100644 index 0000000000..a5ce295d8a Binary files /dev/null and b/tests/PhpWordTests/_files/documents/reader.doc differ diff --git a/tests/PhpWordTests/_files/documents/reader.docx b/tests/PhpWordTests/_files/documents/reader.docx new file mode 100644 index 0000000000..65c761e0a6 Binary files /dev/null and b/tests/PhpWordTests/_files/documents/reader.docx differ diff --git a/tests/PhpWord/Tests/_files/documents/reader.docx.zip b/tests/PhpWordTests/_files/documents/reader.docx.zip similarity index 100% rename from tests/PhpWord/Tests/_files/documents/reader.docx.zip rename to tests/PhpWordTests/_files/documents/reader.docx.zip 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/PhpWord/Tests/_files/documents/reader.html b/tests/PhpWordTests/_files/documents/reader.html similarity index 100% rename from tests/PhpWord/Tests/_files/documents/reader.html rename to tests/PhpWordTests/_files/documents/reader.html diff --git a/tests/PhpWord/Tests/_files/documents/reader.odt b/tests/PhpWordTests/_files/documents/reader.odt similarity index 100% rename from tests/PhpWord/Tests/_files/documents/reader.odt rename to tests/PhpWordTests/_files/documents/reader.odt diff --git a/tests/PhpWord/Tests/_files/documents/reader.rtf b/tests/PhpWordTests/_files/documents/reader.rtf similarity index 88% rename from tests/PhpWord/Tests/_files/documents/reader.rtf rename to tests/PhpWordTests/_files/documents/reader.rtf index 400f43a5d9..4cecadeabd 100644 --- a/tests/PhpWord/Tests/_files/documents/reader.rtf +++ b/tests/PhpWordTests/_files/documents/reader.rtf @@ -16,6 +16,6 @@ \pard\nowidctlpar\qc\sa100{\cf0\f0\fs32\b\i I am styled by both font and paragraph style.}\par \pard\nowidctlpar{\cf1\f1\fs40\b\i\ul\strike\super I am inline styled.}\par \par -{\field {\*\fldinst {HYPERLINK "http://www.google.com/"}}{\fldrslt {Google}}}\par +{\field {\*\fldinst {HYPERLINK "https://github.com/PHPOffice/PHPWord"}}{\fldrslt {PHPWord on GitHub}}}\par \par } \ No newline at end of file diff --git a/tests/PhpWord/Tests/_files/documents/sheet.xls b/tests/PhpWordTests/_files/documents/sheet.xls similarity index 100% rename from tests/PhpWord/Tests/_files/documents/sheet.xls rename to tests/PhpWordTests/_files/documents/sheet.xls diff --git a/tests/PhpWordTests/_files/documents/without_table_macros.docx b/tests/PhpWordTests/_files/documents/without_table_macros.docx new file mode 100644 index 0000000000..316a1df762 Binary files /dev/null and b/tests/PhpWordTests/_files/documents/without_table_macros.docx 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/PhpWord/Tests/_files/images/PhpWord.png b/tests/PhpWordTests/_files/images/PhpWord.png similarity index 100% rename from tests/PhpWord/Tests/_files/images/PhpWord.png rename to tests/PhpWordTests/_files/images/PhpWord.png diff --git a/tests/PhpWord/Tests/_files/images/alexz-johnson.pcx b/tests/PhpWordTests/_files/images/alexz-johnson.pcx similarity index 100% rename from tests/PhpWord/Tests/_files/images/alexz-johnson.pcx rename to tests/PhpWordTests/_files/images/alexz-johnson.pcx diff --git a/tests/PhpWord/Tests/_files/images/angela_merkel.tif b/tests/PhpWordTests/_files/images/angela_merkel.tif similarity index 100% rename from tests/PhpWord/Tests/_files/images/angela_merkel.tif rename to tests/PhpWordTests/_files/images/angela_merkel.tif diff --git a/tests/PhpWord/Tests/_files/images/duke_nukem.bmp b/tests/PhpWordTests/_files/images/duke_nukem.bmp similarity index 100% rename from tests/PhpWord/Tests/_files/images/duke_nukem.bmp rename to tests/PhpWordTests/_files/images/duke_nukem.bmp diff --git a/tests/PhpWord/Tests/_files/images/earth.jpg b/tests/PhpWordTests/_files/images/earth.jpg similarity index 100% rename from tests/PhpWord/Tests/_files/images/earth.jpg rename to tests/PhpWordTests/_files/images/earth.jpg diff --git a/tests/PhpWord/Tests/_files/images/firefox.png b/tests/PhpWordTests/_files/images/firefox.png similarity index 100% rename from tests/PhpWord/Tests/_files/images/firefox.png rename to tests/PhpWordTests/_files/images/firefox.png diff --git a/tests/PhpWord/Tests/_files/images/mario.gif b/tests/PhpWordTests/_files/images/mario.gif similarity index 100% rename from tests/PhpWord/Tests/_files/images/mario.gif rename to tests/PhpWordTests/_files/images/mario.gif diff --git a/tests/PhpWord/Tests/_files/images/mars.jpg b/tests/PhpWordTests/_files/images/mars.jpg similarity index 100% rename from tests/PhpWord/Tests/_files/images/mars.jpg rename to tests/PhpWordTests/_files/images/mars.jpg diff --git a/tests/PhpWord/Tests/_files/images/mars_noext_jpg b/tests/PhpWordTests/_files/images/mars_noext_jpg similarity index 100% rename from tests/PhpWord/Tests/_files/images/mars_noext_jpg rename to tests/PhpWordTests/_files/images/mars_noext_jpg 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/images/new-php-logo.png b/tests/PhpWordTests/_files/images/new-php-logo.png new file mode 100644 index 0000000000..6649079930 Binary files /dev/null and b/tests/PhpWordTests/_files/images/new-php-logo.png differ diff --git a/tests/PhpWord/Tests/_files/templates/blank.docx b/tests/PhpWordTests/_files/templates/blank.docx similarity index 100% rename from tests/PhpWord/Tests/_files/templates/blank.docx rename to tests/PhpWordTests/_files/templates/blank.docx diff --git a/tests/PhpWordTests/_files/templates/clone-delete-block.docx b/tests/PhpWordTests/_files/templates/clone-delete-block.docx new file mode 100644 index 0000000000..986742e369 Binary files /dev/null and b/tests/PhpWordTests/_files/templates/clone-delete-block.docx 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/PhpWord/Tests/_files/templates/clone-merge.docx b/tests/PhpWordTests/_files/templates/clone-merge.docx similarity index 100% rename from tests/PhpWord/Tests/_files/templates/clone-merge.docx rename to tests/PhpWordTests/_files/templates/clone-merge.docx diff --git a/tests/PhpWord/Tests/_files/templates/corrupted_main_document_part.docx b/tests/PhpWordTests/_files/templates/corrupted_main_document_part.docx similarity index 100% rename from tests/PhpWord/Tests/_files/templates/corrupted_main_document_part.docx rename to tests/PhpWordTests/_files/templates/corrupted_main_document_part.docx 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/document22-xml.docx b/tests/PhpWordTests/_files/templates/document22-xml.docx new file mode 100644 index 0000000000..206d80f460 Binary files /dev/null and b/tests/PhpWordTests/_files/templates/document22-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/PhpWordTests/_files/templates/header-footer.docx b/tests/PhpWordTests/_files/templates/header-footer.docx new file mode 100644 index 0000000000..dc2e9e8bea Binary files /dev/null and b/tests/PhpWordTests/_files/templates/header-footer.docx differ diff --git a/tests/PhpWordTests/_files/templates/with_table_macros.docx b/tests/PhpWordTests/_files/templates/with_table_macros.docx new file mode 100644 index 0000000000..4653c6f1f7 Binary files /dev/null and b/tests/PhpWordTests/_files/templates/with_table_macros.docx differ diff --git a/tests/PhpWordTests/_files/xml/reader.zip b/tests/PhpWordTests/_files/xml/reader.zip new file mode 100644 index 0000000000..dbe69cbbc0 Binary files /dev/null and b/tests/PhpWordTests/_files/xml/reader.zip differ diff --git a/tests/PhpWord/Tests/_files/xsl/passthrough.xsl b/tests/PhpWordTests/_files/xsl/passthrough.xsl similarity index 76% rename from tests/PhpWord/Tests/_files/xsl/passthrough.xsl rename to tests/PhpWordTests/_files/xsl/passthrough.xsl index 4ab21dd745..b1d656a10f 100644 --- a/tests/PhpWord/Tests/_files/xsl/passthrough.xsl +++ b/tests/PhpWordTests/_files/xsl/passthrough.xsl @@ -1,6 +1,6 @@ +> diff --git a/tests/PhpWord/Tests/_files/xsl/remove_tables_by_needle.xsl b/tests/PhpWordTests/_files/xsl/remove_tables_by_needle.xsl similarity index 100% rename from tests/PhpWord/Tests/_files/xsl/remove_tables_by_needle.xsl rename to tests/PhpWordTests/_files/xsl/remove_tables_by_needle.xsl diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 4932a21b26..d94d68d880 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,5 @@