diff --git a/.gitattributes b/.gitattributes index 91b0574a08..c5c7c3067f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,7 +3,8 @@ /.gitattributes export-ignore /.github export-ignore /.gitignore export-ignore -/.php_cs.dist export-ignore +/.phpcs.xml.dist export-ignore +/.php-cs-fixer.dist.php export-ignore /.scrutinizer.yml export-ignore /CHANGELOG.PHPExcel.md export-ignore /bin export-ignore @@ -11,6 +12,10 @@ /docs export-ignore /infra export-ignore /mkdocs.yml export-ignore +/phpstan.neon.dist export-ignore +/phpstan-baseline.neon export-ignore +/phpstan-conditional.php export-ignore +/phpunit10.xml.dist export-ignore /phpunit.xml.dist export-ignore /samples export-ignore /tests export-ignore diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index aa35f57f87..55de2af1b9 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -1,8 +1,8 @@ name: GithHub Pages on: push: - tags: - - '*' + branches: + - 'master' jobs: github-pages: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a478a1b6d8..a27babc3fb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,16 +12,17 @@ jobs: - '8.0' - '8.1' - '8.2' - - include: - - php-version: 'nightly' - experimental: true + - '8.3' + - '8.4' name: PHP ${{ matrix.php-version }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Install locales + run: sudo apt-get update && sudo apt-get install -y language-pack-fr language-pack-de - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 @@ -35,7 +36,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -43,7 +44,7 @@ jobs: - name: Delete composer lock file id: composer-lock - if: ${{ matrix.php-version == '8.1' || matrix.php-version == '8.2' || matrix.php-version == 'nightly' }} + if: ${{ matrix.php-version == '8.1' || matrix.php-version == '8.2' || matrix.php-version == '8.3' || matrix.php-version == '8.4' || matrix.php-version == 'nightly'}} run: | rm composer.lock echo "flags=--ignore-platform-reqs" >> $GITHUB_OUTPUT @@ -66,19 +67,37 @@ jobs: - name: "Run PHPUnit tests 2 (Experimental: ${{ matrix.experimental }})" env: FAILURE_ACTION: "${{ matrix.experimental == true }}" - if: ${{ matrix.php-version == '8.1' || matrix.php-version == '8.2' || matrix.php-version == 'nightly' }} - run: vendor/bin/phpunit -c phpunit10.xml.dist --display-incomplete --display-skipped --display-deprecations --display-errors --display-notices --display-warnings || $FAILURE_ACTION + if: ${{ matrix.php-version == '8.1' || matrix.php-version == '8.2' || matrix.php-version == '8.3' || matrix.php-version == '8.4' || matrix.php-version == 'nightly'}} + run: vendor/bin/phpunit --verbose || $FAILURE_ACTION + + find-polyfill: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none + + - name: Find code that might require polyfill + run: php ./bin/findpolyfill.php php-cs-fixer: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.3 extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib coverage: none tools: cs2pr @@ -88,7 +107,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -104,12 +123,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.3 extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib coverage: none tools: cs2pr @@ -119,7 +138,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -135,12 +154,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.3 extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib coverage: none tools: cs2pr @@ -150,7 +169,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -166,12 +185,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.3 extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib coverage: none tools: cs2pr @@ -181,7 +200,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -193,46 +212,11 @@ jobs: - name: Static analysis with PHPStan run: ./vendor/bin/phpstan analyse - coverage: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Setup PHP, with composer and extensions - 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: pcov - - - name: Get composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: composer install --no-progress --prefer-dist --optimize-autoloader - - - name: Coverage - run: | - ./vendor/bin/phpunit --coverage-clover coverage-clover.xml - composer global require scrutinizer/ocular - ~/.composer/vendor/bin/ocular code-coverage:upload --format=php-clover coverage-clover.xml - release: runs-on: ubuntu-latest if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.ref }} # Otherwise our annotated tag is not fetched and we cannot get correct version @@ -244,3 +228,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token with: bodyFile: release-body.txt + makeLatest: false diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 4179c6771e..835c377431 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -2,12 +2,14 @@ $finder = PhpCsFixer\Finder::create() ->exclude('vendor') + ->notPath('src/PhpSpreadsheet/Writer/ZipStream3.php') ->in(__DIR__); $config = new PhpCsFixer\Config(); $config ->setRiskyAllowed(true) ->setFinder($finder) + ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect(null, 600)) ->setCacheFile(sys_get_temp_dir() . '/php-cs-fixer' . preg_replace('~\W~', '-', __DIR__)) ->setRules([ 'align_multiline_comment' => true, @@ -17,17 +19,16 @@ 'binary_operator_spaces' => true, 'blank_line_after_namespace' => true, 'blank_line_after_opening_tag' => true, - 'blank_line_before_statement' => true, - 'braces' => true, + 'blank_line_before_statement' => false, + 'blank_lines_before_namespace' => ['max_line_breaks' => 2, 'min_line_breaks' => 2], // we want 1 blank line before namespace '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, + 'compact_nullable_type_declaration' => true, 'concat_space' => ['spacing' => 'one'], 'constant_case' => true, 'date_time_immutable' => false, // Break our unit tests @@ -41,7 +42,6 @@ '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 @@ -51,10 +51,9 @@ 'fopen_flag_order' => true, 'fopen_flags' => true, 'full_opening_tag' => true, - 'fully_qualified_strict_types' => true, + 'fully_qualified_strict_types' => false, 'function_declaration' => true, 'function_to_constant' => true, - 'function_typehint_space' => true, 'general_phpdoc_annotation_remove' => ['annotations' => ['access', 'category', 'copyright']], 'global_namespace_import' => true, 'header_comment' => false, // We don't use common header in all our files @@ -83,14 +82,12 @@ '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, + 'new_with_parentheses' => ['anonymous_class' => true, 'named_class' => 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, @@ -109,19 +106,17 @@ '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_superfluous_phpdoc_tags' => ['allow_mixed' => true], + 'no_trailing_comma_in_singleline' => ['elements' => ['arguments', 'array_destructuring', 'array', 'group_import']], 'no_trailing_whitespace' => true, 'no_trailing_whitespace_in_comment' => true, 'no_unneeded_control_parentheses' => true, - 'no_unneeded_curly_braces' => true, + 'no_unneeded_braces' => true, 'no_unneeded_final_method' => true, 'no_unreachable_default_argument_value' => true, 'no_unset_cast' => true, - 'no_unset_on_property' => true, + 'no_unset_on_property' => false, 'no_unused_imports' => true, 'no_useless_else' => true, 'no_useless_return' => true, @@ -177,7 +172,7 @@ 'phpdoc_trim' => true, 'phpdoc_trim_consecutive_blank_line_separation' => true, 'phpdoc_types' => true, - 'phpdoc_types_order' => true, + 'phpdoc_types_order' => false, 'phpdoc_var_annotation_correct_order' => true, 'phpdoc_var_without_name' => true, 'pow_to_exponentiation' => true, @@ -195,20 +190,21 @@ '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_quote' => false, 'single_trait_insert_per_statement' => true, 'space_after_semicolon' => true, + 'spaces_inside_parentheses' => ['space' => 'none'], '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_implicit_backslashes' => ['single_quoted' => 'unescape', 'double_quoted' => 'escape', 'heredoc' => 'escape'], // was escape_implicit_backslashes 'string_line_ending' => true, 'switch_case_semicolon_to_colon' => true, 'switch_case_space' => true, @@ -216,6 +212,7 @@ 'ternary_to_null_coalescing' => true, 'trailing_comma_in_multiline' => true, 'trim_array_spaces' => true, + 'type_declaration_spaces' => ['elements' => ['function', 'property']], // was function_typehint_space 'unary_operator_spaces' => true, 'visibility_required' => ['elements' => ['property', 'method']], // not const 'void_return' => true, diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000000..c67101532d --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,12 @@ +# Read the Docs configuration file for MkDocs projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3" + +mkdocs: + configuration: mkdocs.yml diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index c32d10407a..0000000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,30 +0,0 @@ -checks: - php: true - -coding_style: - php: - spaces: - before_parentheses: - closure_definition: true - around_operators: - concatenation: true - -build: - nodes: - analysis: - image: default-bionic - environment: - php: 8.1 - tests: - override: - - php-scrutinizer-run - -tools: - external_code_coverage: - timeout: 600 - -build_failure_conditions: - - 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed - - 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity - - 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection - - 'patches.label("Unused Use Statements").new.exists' # No new unused imports patches allowed diff --git a/CHANGELOG.md b/CHANGELOG.md index d5f0fc823e..cdadcaf897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,171 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com) -and this project adheres to [Semantic Versioning](https://semver.org). +and this project adheres to [Semantic Versioning](https://semver.org). This is always true of the master branch. Some earlier branches, including the branch from which you are reading this file, remain supported and security fixes are applied to them; if the security fix represents a breaking change, it may have to be applied as a minor or patch version. -## Unreleased - TBD +## 2026-04-19 - 1.30.4 + +### Fixed + +- Security patches. + +## 2026-04-09 - 1.30.3 + +### Fixed + +- Security patches. +- Option to whitelist external images. Security-related backport of [PR #4793](https://github.com/PHPOffice/PhpSpreadsheet/pull/4793) + +## 2026-01-10 - 1.30.2 + +### Changed + +- Evaluation of WEBSERVICE no longer requires external client, but will use oldCalculatedValue unless the request is for a domain in a user-supplied whitelist. Security-related backport of [PR #4751](https://github.com/PHPOffice/PhpSpreadsheet/pull/4751) + +### Deprecated + +- Settings methods setHttpClient, unsetHttpClient, getHttpClient, and getRequestFactory are no longer used. No replacement. + +### Fixed + +- Changes to WEBSERVICE. Backport of [PR #4751](https://github.com/PHPOffice/PhpSpreadsheet/pull/4751) + +## 2025-10-25 - 1.30.1 + +### Functionally Frozen + +- Except for security changes, no further maintenance will be applied to this branch. +You are encouraged to upgrade to a maintained branch as soon as possible. +Maintained branches are master (preferred - version is 5.4.0 as of the date when this is being written), 3.10.x (current version is 3.10.3), and 2.4.x (2.4.3). +- Of particular note is that this branch should not run under Php 8.5+, and will *not* be updated to avoid deprecation notices introduced with Php 8.5. + +## 2025-08-10 - 1.30.0 + +### Breaking Changes + +- Images will be loaded from an external source (e.g. http://example.com/img.png) only if the reader is explicitly set to allow it via `$reader->setAllowExternalImages(true)`. We do not believe that loading of external images is a widely used feature. This is a necessary change for security purposes. It unfortunately breaks Semantic Versioning for reasons described above; there is no way to start a new major version for this branch. + +# 2025-07-23 - 1.29.12 + +### Added + +- Add to all readers the option to allow or forbid fetching external images. This is unconditionally allowed now. The default will be set to "allow", so no code changes are necessary. However, we are giving consideration to changing the default.[PR #4545](https://github.com/PHPOffice/PhpSpreadsheet/pull/4545) + +# 2025-06-22 - 1.29.11 + +### Changed + +- Allow php-cs-fixer to Handle Implicit Backslashes. + +### Added + +- Allow spreadsheet to be serialized. [PR #4405](https://github.com/PHPOffice/PhpSpreadsheet/pull/4405) + +### Fixed + +- TEXT and TIMEVALUE functions. [Issue #4249](https://github.com/PHPOffice/PhpSpreadsheet/issues/4249) [PR #4352](https://github.com/PHPOffice/PhpSpreadsheet/pull/4352) +- Removing Columns/Rows Containing Merged Cells. Backport of [PR #4465](https://github.com/PHPOffice/PhpSpreadsheet/pull/4465) +- Allow Xlsx Reader to Specify ParseHuge. [Issue #4260](https://github.com/PHPOffice/PhpSpreadsheet/issues/4260) [PR #4515](https://github.com/PHPOffice/PhpSpreadsheet/pull/4515) + +# 2025-02-07 - 1.29.10 + +### Changed + +- Allow version 1 and 2 of `composer/pcre`. + +### Fixed + +- Xls writer Parser Mishandling True/False Argument. Backport of [PR #4333](https://github.com/PHPOffice/PhpSpreadsheet/pull/4333) +- Xls writer Parser Parse By Character Not Byte. Backport of [PR #4344](https://github.com/PHPOffice/PhpSpreadsheet/pull/4344) + +# 2025-01-26 - 1.29.9 + +### Fixed + +- Backported security patch for control characters in protocol. +- Use Composer\Pcre in Xls/Parser. Partial backport of [PR #4203](https://github.com/PHPOffice/PhpSpreadsheet/pull/4203) + +# 2025-01-11 - 1.29.8 + +### Deprecated + +- Worksheet::getHashCode is no longer needed. + +### Fixed + +- Backported security patch for Html navigation. +- Change hash code for worksheet. Backport of [PR #4207](https://github.com/PHPOffice/PhpSpreadsheet/pull/4207) +- Retitling cloned worksheets. Backport of [PR #4302](https://github.com/PHPOffice/PhpSpreadsheet/pull/4302) + +# 2024-12-26 - 1.29.7 + +### Deprecated + +- Drawing::setIsUrl is unneeded. The property is set when setPath determines whether path is a url. + +### Fixed + +- More context options may be needed for http(s) image. Backport of [PR #4276](https://github.com/PHPOffice/PhpSpreadsheet/pull/4276) +- Backported security patches for Samples. +- Backported security patches for Html Writer. + +## 1.29.6 - 2024-12-08 + +### Fixed + +- Fix Minor Break Handling Drawings. Backport of [PR #4244](https://github.com/PHPOffice/PhpSpreadsheet/pull/4244) +- Upgrade locked version of Tcpdf (security advisory). +- Upgrade locked version of Dompdf (Php8.4 compatibility). +- Remove unnecessary files from Composer package. + +## 1.29.5 - 2024-11-22 + +### Changed + +- Settings::libXmlLoaderOptions is ignored. Backport of [PR #4233](https://github.com/PHPOffice/PhpSpreadsheet/pull/4233) + +### Deprecated + +- Settings::setLibXmlLoaderOptions() and Settings::getLibXmlLoaderOptions() are no longer needed - no replacement. + +## 1.29.4 - 2024-11-10 + +### Fixed + +- 1.29.3 omitted +- Backported security patches. +- Write ignoredErrors Tag Before Drawings. Backport of [PR #4212](https://github.com/PHPOffice/PhpSpreadsheet/pull/4212) intended for 3.4.0. +- Changes to ROUNDDOWN/ROUNDUP/TRUNC. Backport of [PR #4214](https://github.com/PHPOffice/PhpSpreadsheet/pull/4214) intended for 3.4.0. +- Replace str_starts_with in Drawing. [Issue #4215](https://github.com/PHPOffice/PhpSpreadsheet/issues/4215) + +### Added + +- Method to Test Whether Csv Will Be Affected by Php9. Backport of [PR #4189](https://github.com/PHPOffice/PhpSpreadsheet/pull/4189) intended for 3.4.0. + +## 1.29.2 - 2024-09-29 + +### Fixed + +- Backported security patches. +- Support for Php8.4. +- Change to Csv Reader (see below under Deprecated). Backport of PR #4162 intended for 3.0.0. [Issue #4161](https://github.com/PHPOffice/PhpSpreadsheet/issues/4161) +- Tweaks to ROUNDUP, ROUNDDOWN, TRUNC, AMORDEGRC (results had been different under 8.4). + +### Changed + +- Images will not be added to spreadsheet if they cannot be validated as images. + +### Deprecated + +- Php8.4 will deprecate the escape parameter of fgetcsv. Csv Reader is affected by this; code is changed to be unaffected, but this will mean a breaking change is coming with Php9. Any code which uses the default escape value of backslash will fail in Php9. It is recommended to explicitly set the escape value to null string before then. + +## 1.29.1 - 2024-09-03 + +### Fixed + +- Backported security patches. + +## 1.29.0 - 2023-06-15 ### Added @@ -15,6 +177,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Font/Effects/Theme support for Chart Data Labels and Axis. [PR #3476](https://github.com/PHPOffice/PhpSpreadsheet/pull/3476) - Font Themes support. [PR #3486](https://github.com/PHPOffice/PhpSpreadsheet/pull/3486) - Ability to Ignore Cell Errors in Excel. [Issue #1141](https://github.com/PHPOffice/PhpSpreadsheet/issues/1141) [PR #3508](https://github.com/PHPOffice/PhpSpreadsheet/pull/3508) +- Unzipped Gnumeric file [PR #3591](https://github.com/PHPOffice/PhpSpreadsheet/pull/3591) ### Changed @@ -56,7 +219,14 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Handle REF error as part of range [Issue #3453](https://github.com/PHPOffice/PhpSpreadsheet/issues/3453) [PR #3467](https://github.com/PHPOffice/PhpSpreadsheet/pull/3467) - Handle Absolute Pathnames in Rels File [Issue #3553](https://github.com/PHPOffice/PhpSpreadsheet/issues/3553) [PR #3554](https://github.com/PHPOffice/PhpSpreadsheet/pull/3554) - Return Page Breaks in Order [Issue #3552](https://github.com/PHPOffice/PhpSpreadsheet/issues/3552) [PR #3555](https://github.com/PHPOffice/PhpSpreadsheet/pull/3555) - +- Add position attribute for MemoryDrawing in Html [Issue #3529](https://github.com/PHPOffice/PhpSpreadsheet/issues/3529 [PR #3535](https://github.com/PHPOffice/PhpSpreadsheet/pull/3535) +- Allow Index_number as Array for VLOOKUP/HLOOKUP [Issue #3561](https://github.com/PHPOffice/PhpSpreadsheet/issues/3561 [PR #3570](https://github.com/PHPOffice/PhpSpreadsheet/pull/3570) +- Add Unsupported Options in Xml Spreadsheet [Issue #3566](https://github.com/PHPOffice/PhpSpreadsheet/issues/3566 [Issue #3568](https://github.com/PHPOffice/PhpSpreadsheet/issues/3568 [Issue #3569](https://github.com/PHPOffice/PhpSpreadsheet/issues/3569 [PR #3567](https://github.com/PHPOffice/PhpSpreadsheet/pull/3567) +- Changes to NUMBERVALUE, VALUE, DATEVALUE, TIMEVALUE [Issue #3574](https://github.com/PHPOffice/PhpSpreadsheet/issues/3574 [PR #3575](https://github.com/PHPOffice/PhpSpreadsheet/pull/3575) +- Redo calculation of color tinting [Issue #3550](https://github.com/PHPOffice/PhpSpreadsheet/issues/3550) [PR #3580](https://github.com/PHPOffice/PhpSpreadsheet/pull/3580) +- Accommodate Slash with preg_quote [PR #3582](https://github.com/PHPOffice/PhpSpreadsheet/pull/3582) [PR #3583](https://github.com/PHPOffice/PhpSpreadsheet/pull/3583) [PR #3584](https://github.com/PHPOffice/PhpSpreadsheet/pull/3584) +- HyperlinkBase Property and Html Handling of Properties [Issue #3573](https://github.com/PHPOffice/PhpSpreadsheet/issues/3573) [PR #3589](https://github.com/PHPOffice/PhpSpreadsheet/pull/3589) +- Improvements for Data Validation [Issue #3592](https://github.com/PHPOffice/PhpSpreadsheet/issues/3592) [Issue #3594](https://github.com/PHPOffice/PhpSpreadsheet/issues/3594) [PR #3605](https://github.com/PHPOffice/PhpSpreadsheet/pull/3605) ## 1.28.0 - 2023-02-25 diff --git a/LICENSE b/LICENSE index 3ec5723dde..e4bc4a3ae4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 PhpSpreadsheet Authors +Copyright (c) 2019-2026 PhpSpreadsheet Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 79e6e80def..af83bbcae3 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,33 @@ # PhpSpreadsheet [![Build Status](https://github.com/PHPOffice/PhpSpreadsheet/workflows/main/badge.svg)](https://github.com/PHPOffice/PhpSpreadsheet/actions) -[![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/?branch=master) -[![Code Coverage](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/?branch=master) +[![Code Coverage](https://coveralls.io/repos/github/PHPOffice/PhpSpreadsheet/badge.svg?branch=master)](https://coveralls.io/github/PHPOffice/PhpSpreadsheet?branch=master) [![Total Downloads](https://img.shields.io/packagist/dt/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet) [![Latest Stable Version](https://img.shields.io/github/v/release/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet) -[![License](https://img.shields.io/github/license/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet) +[![License](https://poser.pugx.org/phpoffice/phpspreadsheet/license)](https://packagist.org/packages/phpoffice/phpspreadsheet) [![Join the chat at https://gitter.im/PHPOffice/PhpSpreadsheet](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/PHPOffice/PhpSpreadsheet) PhpSpreadsheet is a library written in pure PHP and offers a set of classes that allow you to read and write various spreadsheet file formats such as Excel and LibreOffice Calc. +This branch (1.30.x) is *not* the latest version of PhpSpreadsheet, and may therefore lack features and bug fixes found in the latest version. + +## Security Changes Only + +Except for security changes, no further maintenance will be applied to this branch. +You are encouraged to upgrade to a maintained branch as soon as possible. +Maintained branches are master (preferred - version is 5.4.0 as of the date when this is being written), 3.10.x (current version is 3.10.3), and 2.4.x (2.4.3). + +Of particular note is that this branch should not run under Php 8.5, and will *not* be updated to avoid deprecation notices which will be introduced with Php 8.5. + ## PHP Version Support -LTS: Support for PHP versions will only be maintained for a period of six months beyond the +LTS: For maintained branches, support for PHP versions will only be maintained for a period of six months beyond the [end of life](https://www.php.net/supported-versions) of that PHP version. Currently the required PHP minimum version is PHP __7.4__, and we [will support that version](https://www.php.net/eol.php) until 28th June 2023. +However, since this branch is no longer maintained, that policy is not enforced. +The PHP maximum version supported by this release is PHP __8.4__. See the `composer.json` for other requirements. @@ -74,7 +85,7 @@ or the appropriate PDF Writer wrapper for the library that you have chosen to in For Chart export, we support following packages, which you will also need to install yourself using `composer require` - [jpgraph/jpgraph](https://packagist.org/packages/jpgraph/jpgraph) (this package was abandoned at version 4.0. You can manually download the latest version that supports PHP 8 and above from [jpgraph.net](https://jpgraph.net/)) - - [mitoteam/jpgraph](https://packagist.org/packages/mitoteam/jpgraph) - fork with modern PHP versions support. + - [mitoteam/jpgraph](https://packagist.org/packages/mitoteam/jpgraph) - up to date fork with modern PHP versions support and some bugs fixed. and then configure PhpSpreadsheet using: ```php diff --git a/bin/findpolyfill.php b/bin/findpolyfill.php new file mode 100644 index 0000000000..941865b88b --- /dev/null +++ b/bin/findpolyfill.php @@ -0,0 +1,54 @@ +#!/usr/bin/env php +getExtension() === 'php') { + $fullname = $it->getPath() . '/' . $it->getBaseName(); + $contents = file_get_contents($fullname); + if ($contents === false) { + echo "failed to read $fullname\n"; + ++$retCode; + } elseif (preg_match_all($polyfill, $contents, $matches)) { + var_dump($fullname, $matches); + ++$retCode; + } + } + } + + return $retCode; +} + +// Don't care if tests use polyfill +$errors = findPolyfill(__DIR__ . '/../src') + findPolyfill(__DIR__ . '/../samples'); +if ($errors !== 0) { + echo "Found $errors files that might require polyfills\n"; + exit(1); +} +echo "No polyfills needed\n"; diff --git a/composer.json b/composer.json index 3678c8eb62..d935d3758e 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "spreadsheet" ], "config": { + "process-timeout": 600, "sort-packages": true, "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true @@ -38,6 +39,9 @@ }, { "name": "Adrien Crivelli" + }, + { + "name": "Owen Leibman" } ], "scripts": { @@ -61,7 +65,7 @@ ] }, "require": { - "php": "^7.4 || ^8.0", + "php": ">=7.4.0 <8.5.0", "ext-ctype": "*", "ext-dom": "*", "ext-fileinfo": "*", @@ -75,24 +79,24 @@ "ext-xmlwriter": "*", "ext-zip": "*", "ext-zlib": "*", + "composer/pcre": "^1||^2||^3", "ezyang/htmlpurifier": "^4.15", - "maennchen/zipstream-php": "^2.1", + "maennchen/zipstream-php": "^2.1 || ^3.0", "markbaker/complex": "^3.0", "markbaker/matrix": "^3.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "dev-main", - "dompdf/dompdf": "^1.0 || ^2.0", + "doctrine/instantiator": "^1.5", + "dompdf/dompdf": "^1.0 || ^2.0 || ^3.0", "friendsofphp/php-cs-fixer": "^3.2", - "mitoteam/jpgraph": "^10.2.4", + "mitoteam/jpgraph": "^10.3", "mpdf/mpdf": "^8.1.1", "phpcompatibility/php-compatibility": "^9.3", "phpstan/phpstan": "^1.1", "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^8.5 || ^9.0 || ^10.0", + "phpunit/phpunit": "^8.5 || ^9.0", "squizlabs/php_codesniffer": "^3.7", "tecnickcom/tcpdf": "^6.5" }, diff --git a/composer.lock b/composer.lock index 3e89fec89a..f95a93bf11 100644 --- a/composer.lock +++ b/composer.lock @@ -4,24 +4,103 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "06031132b41198f02eff4f6289d43ae2", + "content-hash": "ac16e6d7670a4e7987786de79c3ea3df", "packages": [ + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, { "name": "ezyang/htmlpurifier", - "version": "v4.16.0", + "version": "v4.19.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf", "shasum": "" }, "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { "cerdic/css-tidy": "^1.7 || ^2.0", @@ -63,9 +142,9 @@ ], "support": { "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0" + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0" }, - "time": "2022-09-18T07:06:19+00:00" + "time": "2025-10-17T16:34:55+00:00" }, { "name": "maennchen/zipstream-php", @@ -315,113 +394,6 @@ ], "time": "2022-08-04T09:53:51+00:00" }, - { - "name": "psr/http-client", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", - "keywords": [ - "http", - "http-client", - "psr", - "psr-18" - ], - "support": { - "source": "https://github.com/php-fig/http-client/tree/1.0.2" - }, - "time": "2023-04-10T20:12:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" - }, - "time": "2023-04-10T20:10:41+00:00" - }, { "name": "psr/http-message", "version": "1.1", @@ -528,16 +500,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -551,9 +523,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -591,7 +560,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -607,41 +576,36 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-06-19T12:30:46+00:00" } ], "packages-dev": [ { - "name": "composer/pcre", - "version": "3.1.0", + "name": "clue/ndjson-react", + "version": "v1.3.0", "source": { "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "php": ">=5.3", + "react/stream": "^1.2" }, "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, "autoload": { "psr-4": { - "Composer\\Pcre\\": "src" + "Clue\\React\\NDJson\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -650,50 +614,48 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Christian Lück", + "email": "christian@clue.engineering" } ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" ], "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.0" + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" }, "funding": [ { - "url": "https://packagist.com", + "url": "https://clue.engineering/support", "type": "custom" }, { - "url": "https://github.com/composer", + "url": "https://github.com/clue", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2022-11-17T09:50:14+00:00" + "time": "2022-12-23T10:58:28+00:00" }, { "name": "composer/semver", - "version": "3.3.2", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", "shasum": "" }, "require": { @@ -743,9 +705,9 @@ "versioning" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" + "source": "https://github.com/composer/semver/tree/3.4.2" }, "funding": [ { @@ -761,20 +723,20 @@ "type": "tidelift" } ], - "time": "2022-04-01T19:23:25+00:00" + "time": "2024-07-12T11:35:52+00:00" }, { "name": "composer/xdebug-handler", - "version": "3.0.3", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { @@ -785,7 +747,7 @@ "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { @@ -809,9 +771,9 @@ "performance" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { @@ -827,7 +789,7 @@ "type": "tidelift" } ], - "time": "2022-02-25T21:32:43+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { "name": "dealerdirect/phpcodesniffer-composer-installer", @@ -835,12 +797,12 @@ "source": { "type": "git", "url": "https://github.com/PHPCSStandards/composer-installer.git", - "reference": "4be43904336affa5c2f70744a348312336afd0da" + "reference": "026a90d8ca32603c5bacc0d89292937fb53adb67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", - "reference": "4be43904336affa5c2f70744a348312336afd0da", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/026a90d8ca32603c5bacc0d89292937fb53adb67", + "reference": "026a90d8ca32603c5bacc0d89292937fb53adb67", "shasum": "" }, "require": { @@ -873,9 +835,9 @@ "authors": [ { "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" + "email": "opensource@frenck.dev", + "homepage": "https://frenck.dev", + "role": "Open source developer" }, { "name": "Contributors", @@ -883,7 +845,6 @@ } ], "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", "keywords": [ "PHPCodeSniffer", "PHP_CodeSniffer", @@ -904,128 +865,24 @@ ], "support": { "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", "source": "https://github.com/PHPCSStandards/composer-installer" }, - "time": "2023-01-05T11:28:13+00:00" - }, - { - "name": "doctrine/annotations", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^2 || ^3", - "ext-tokenizer": "*", - "php": "^7.2 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^2.0", - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^5.4 || ^6", - "vimeo/psalm": "^4.10" - }, - "suggest": { - "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, + "funding": [ { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" + "url": "https://github.com/PHPCSStandards", + "type": "github" }, { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "url": "https://github.com/jrfnl", + "type": "github" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.1" - }, - "time": "2023-02-02T22:02:53+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5|^8.5|^9.5", - "psr/log": "^1|^2|^3" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" - }, - "time": "2022-05-02T15:47:09+00:00" + "time": "2024-08-26T10:00:24+00:00" }, { "name": "doctrine/instantiator", @@ -1098,194 +955,321 @@ "time": "2022-12-30T00:15:36+00:00" }, { - "name": "doctrine/lexer", - "version": "2.1.0", + "name": "dompdf/dompdf", + "version": "v3.0.1", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" + "url": "https://github.com/dompdf/dompdf.git", + "reference": "2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6", + "reference": "2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.0", + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.0" + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" }, - "type": "library", + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "src" - } + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "LGPL-2.1" ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" } ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.0" + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.0.1" }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, + "time": "2024-12-05T14:59:38+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" } ], - "time": "2022-12-14T08:49:07+00:00" + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.1" + }, + "time": "2024-12-02T14:37:59+00:00" }, { - "name": "dompdf/dompdf", - "version": "v2.0.3", + "name": "dompdf/php-svg-lib", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/dompdf/dompdf.git", - "reference": "e8d2d5e37e8b0b30f0732a011295ab80680d7e85" + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/e8d2d5e37e8b0b30f0732a011295ab80680d7e85", - "reference": "e8d2d5e37e8b0b30f0732a011295ab80680d7e85", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af", "shasum": "" }, "require": { - "ext-dom": "*", "ext-mbstring": "*", - "masterminds/html5": "^2.0", - "phenx/php-font-lib": ">=0.5.4 <1.0.0", - "phenx/php-svg-lib": ">=0.3.3 <1.0.0", - "php": "^7.1 || ^8.0" + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4" }, "require-dev": { - "ext-json": "*", - "ext-zip": "*", - "mockery/mockery": "^1.3", - "phpunit/phpunit": "^7.5 || ^8 || ^9", - "squizlabs/php_codesniffer": "^3.5" + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" }, - "suggest": { - "ext-gd": "Needed to process images", - "ext-gmagick": "Improves image processing performance", - "ext-imagick": "Improves image processing performance", - "ext-zlib": "Needed for pdf stream compression" + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0" + }, + "time": "2024-04-29T13:26:35+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" }, "type": "library", "autoload": { "psr-4": { - "Dompdf\\": "src/" - }, - "classmap": [ - "lib/" - ] + "Evenement\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-2.1" + "MIT" ], "authors": [ { - "name": "The Dompdf Community", - "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" } ], - "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", - "homepage": "https://github.com/dompdf/dompdf", + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], "support": { - "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v2.0.3" + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "8520451a140d3f46ac33042715115e290cf5785f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" }, - "time": "2023-02-07T12:51:48+00:00" + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-08-06T10:04:20+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.16.0", + "version": "v3.64.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "d40f9436e1c448d309fa995ab9c14c5c7a96f2dc" + "reference": "58dd9c931c785a79739310aef5178928305ffa67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/d40f9436e1c448d309fa995ab9c14c5c7a96f2dc", - "reference": "d40f9436e1c448d309fa995ab9c14c5c7a96f2dc", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/58dd9c931c785a79739310aef5178928305ffa67", + "reference": "58dd9c931c785a79739310aef5178928305ffa67", "shasum": "" }, "require": { - "composer/semver": "^3.3", + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.3", - "doctrine/annotations": "^2", - "doctrine/lexer": "^2 || ^3", + "ext-filter": "*", "ext-json": "*", "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.0", "php": "^7.4 || ^8.0", - "sebastian/diff": "^4.0 || ^5.0", - "symfony/console": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/filesystem": "^5.4 || ^6.0", - "symfony/finder": "^5.4 || ^6.0", - "symfony/options-resolver": "^5.4 || ^6.0", - "symfony/polyfill-mbstring": "^1.27", - "symfony/polyfill-php80": "^1.27", - "symfony/polyfill-php81": "^1.27", - "symfony/process": "^5.4 || ^6.0", - "symfony/stopwatch": "^5.4 || ^6.0" + "react/child-process": "^0.6.5", + "react/event-loop": "^1.0", + "react/promise": "^2.0 || ^3.0", + "react/socket": "^1.0", + "react/stream": "^1.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.3", + "infection/infection": "^0.29.5", "justinrainbow/json-schema": "^5.2", - "keradus/cli-executor": "^2.0", + "keradus/cli-executor": "^2.1", "mikey179/vfsstream": "^1.6.11", - "php-coveralls/php-coveralls": "^2.5.3", + "php-coveralls/php-coveralls": "^2.7", "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy": "^1.16", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "phpunitgoodpractices/polyfill": "^1.6", - "phpunitgoodpractices/traits": "^1.9.2", - "symfony/phpunit-bridge": "^6.2.3", - "symfony/yaml": "^5.4 || ^6.0" + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", + "phpunit/phpunit": "^9.6.19 || ^10.5.21 || ^11.2", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -1298,7 +1282,10 @@ "autoload": { "psr-4": { "PhpCsFixer\\": "src/" - } + }, + "exclude-from-classmap": [ + "src/Fixer/Internal/*" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1323,7 +1310,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.16.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.64.0" }, "funding": [ { @@ -1331,30 +1318,28 @@ "type": "github" } ], - "time": "2023-04-02T19:30:06+00:00" + "time": "2024-08-30T23:09:38+00:00" }, { "name": "masterminds/html5", - "version": "2.7.6", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "897eb517a343a2281f11bc5556d6548db7d93947" + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/897eb517a343a2281f11bc5556d6548db7d93947", - "reference": "897eb517a343a2281f11bc5556d6548db7d93947", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-dom": "*", - "ext-libxml": "*", "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7" + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" }, "type": "library", "extra": { @@ -1398,29 +1383,30 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.7.6" + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" }, - "time": "2022-08-18T16:18:26+00:00" + "time": "2024-03-31T07:05:07+00:00" }, { "name": "mitoteam/jpgraph", - "version": "10.2.6", + "version": "10.4.4", "source": { "type": "git", "url": "https://github.com/mitoteam/jpgraph.git", - "reference": "a36181cf31918b810d809816458083c4fac7d7e6" + "reference": "9ad8e2fcc30f765c788a28543e9705fb541d499f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mitoteam/jpgraph/zipball/a36181cf31918b810d809816458083c4fac7d7e6", - "reference": "a36181cf31918b810d809816458083c4fac7d7e6", + "url": "https://api.github.com/repos/mitoteam/jpgraph/zipball/9ad8e2fcc30f765c788a28543e9705fb541d499f", + "reference": "9ad8e2fcc30f765c788a28543e9705fb541d499f", "shasum": "" }, "require": { "php": ">=5.5" }, "replace": { - "jpgraph/jpgraph": "4.0.2" + "jpgraph/jpgraph": "4.0.2", + "rulin132/jpgraph": "4.3.5" }, "type": "library", "autoload": { @@ -1434,43 +1420,52 @@ ], "authors": [ { - "name": "JpGraph team" + "name": "JpGraph team", + "homepage": "https://jpgraph.net", + "role": "Original Library Developers" + }, + { + "name": "MiTo Team", + "email": "info@mito-team.com", + "homepage": "https://www.mito-team.com", + "role": "Composer package maintenance, PHP-compatibility patches" } ], - "description": "JpGraph library composer package with PHP 8.2 support", + "description": "JpGraph library composer package with PHP 8.4 support", "homepage": "https://github.com/mitoteam/jpgraph", "keywords": [ "jpgraph" ], "support": { "issues": "https://github.com/mitoteam/jpgraph/issues", - "source": "https://github.com/mitoteam/jpgraph/tree/10.2.6" + "source": "https://github.com/mitoteam/jpgraph/tree/10.4.4" }, - "time": "2023-03-10T11:02:47+00:00" + "time": "2025-01-01T05:39:20+00:00" }, { "name": "mpdf/mpdf", - "version": "v8.1.5", + "version": "v8.2.6", "source": { "type": "git", "url": "https://github.com/mpdf/mpdf.git", - "reference": "c264ce27af0d794ecd04e201b7e37a06b8a9d720" + "reference": "dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mpdf/mpdf/zipball/c264ce27af0d794ecd04e201b7e37a06b8a9d720", - "reference": "c264ce27af0d794ecd04e201b7e37a06b8a9d720", + "url": "https://api.github.com/repos/mpdf/mpdf/zipball/dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44", + "reference": "dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44", "shasum": "" }, "require": { "ext-gd": "*", "ext-mbstring": "*", + "mpdf/psr-http-message-shim": "^1.0 || ^2.0", + "mpdf/psr-log-aware-trait": "^2.0 || ^3.0", "myclabs/deep-copy": "^1.7", "paragonie/random_compat": "^1.4|^2.0|^9.99.99", - "php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0 || ~8.2.0", - "php-http/message-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/log": "^1.0 || ^2.0", + "php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", "setasign/fpdi": "^2.1" }, "require-dev": { @@ -1487,6 +1482,9 @@ }, "type": "library", "autoload": { + "files": [ + "src/functions.php" + ], "psr-4": { "Mpdf\\": "src/" } @@ -1513,7 +1511,7 @@ "utf-8" ], "support": { - "docs": "http://mpdf.github.io", + "docs": "https://mpdf.github.io", "issues": "https://github.com/mpdf/mpdf/issues", "source": "https://github.com/mpdf/mpdf" }, @@ -1523,20 +1521,112 @@ "type": "custom" } ], - "time": "2023-04-04T15:06:48+00:00" + "time": "2025-08-18T08:51:51+00:00" + }, + { + "name": "mpdf/psr-http-message-shim", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/mpdf/psr-http-message-shim.git", + "reference": "3206e6b80b6d2479e148ee497e9f2bebadc919db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/psr-http-message-shim/zipball/3206e6b80b6d2479e148ee497e9f2bebadc919db", + "reference": "3206e6b80b6d2479e148ee497e9f2bebadc919db", + "shasum": "" + }, + "require": { + "psr/http-message": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mpdf\\PsrHttpMessageShim\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Dorison", + "email": "mark@chromatichq.com" + }, + { + "name": "Kristofer Widholm", + "email": "kristofer@chromatichq.com" + }, + { + "name": "Nigel Cunningham", + "email": "nigel.cunningham@technocrat.com.au" + } + ], + "description": "Shim to allow support of different psr/message versions.", + "support": { + "issues": "https://github.com/mpdf/psr-http-message-shim/issues", + "source": "https://github.com/mpdf/psr-http-message-shim/tree/1.0.0" + }, + "time": "2023-09-01T05:59:47+00:00" + }, + { + "name": "mpdf/psr-log-aware-trait", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/mpdf/psr-log-aware-trait.git", + "reference": "7a077416e8f39eb626dee4246e0af99dd9ace275" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/psr-log-aware-trait/zipball/7a077416e8f39eb626dee4246e0af99dd9ace275", + "reference": "7a077416e8f39eb626dee4246e0af99dd9ace275", + "shasum": "" + }, + "require": { + "psr/log": "^1.0 || ^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mpdf\\PsrLogAwareTrait\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Dorison", + "email": "mark@chromatichq.com" + }, + { + "name": "Kristofer Widholm", + "email": "kristofer@chromatichq.com" + } + ], + "description": "Trait to allow support of different psr/log versions.", + "support": { + "issues": "https://github.com/mpdf/psr-log-aware-trait/issues", + "source": "https://github.com/mpdf/psr-log-aware-trait/tree/v2.0.0" + }, + "time": "2023-05-03T06:18:28+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -1544,11 +1634,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -1574,7 +1665,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -1582,29 +1673,31 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-11-08T17:47:46+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.4", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -1612,7 +1705,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -1636,9 +1729,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" }, - "time": "2023-03-05T19:49:14+00:00" + "time": "2024-10-08T18:51:32+00:00" }, { "name": "paragonie/random_compat", @@ -1692,20 +1785,21 @@ }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -1746,9 +1840,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -1801,150 +1901,6 @@ }, "time": "2022-02-21T01:04:05+00:00" }, - { - "name": "phenx/php-font-lib", - "version": "0.5.4", - "source": { - "type": "git", - "url": "https://github.com/dompdf/php-font-lib.git", - "reference": "dd448ad1ce34c63d09baccd05415e361300c35b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/dd448ad1ce34c63d09baccd05415e361300c35b4", - "reference": "dd448ad1ce34c63d09baccd05415e361300c35b4", - "shasum": "" - }, - "require": { - "ext-mbstring": "*" - }, - "require-dev": { - "symfony/phpunit-bridge": "^3 || ^4 || ^5" - }, - "type": "library", - "autoload": { - "psr-4": { - "FontLib\\": "src/FontLib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-3.0" - ], - "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", - "support": { - "issues": "https://github.com/dompdf/php-font-lib/issues", - "source": "https://github.com/dompdf/php-font-lib/tree/0.5.4" - }, - "time": "2021-12-17T19:44:54+00:00" - }, - { - "name": "phenx/php-svg-lib", - "version": "0.5.0", - "source": { - "type": "git", - "url": "https://github.com/dompdf/php-svg-lib.git", - "reference": "76876c6cf3080bcb6f249d7d59705108166a6685" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/76876c6cf3080bcb6f249d7d59705108166a6685", - "reference": "76876c6cf3080bcb6f249d7d59705108166a6685", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": "^7.1 || ^8.0", - "sabberworm/php-css-parser": "^8.4" - }, - "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Svg\\": "src/Svg" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-3.0" - ], - "authors": [ - { - "name": "Fabien Ménager", - "email": "fabien.menager@gmail.com" - } - ], - "description": "A library to read, parse and export to PDF SVG files.", - "homepage": "https://github.com/PhenX/php-svg-lib", - "support": { - "issues": "https://github.com/dompdf/php-svg-lib/issues", - "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.0" - }, - "time": "2022-09-06T12:16:56+00:00" - }, - { - "name": "php-http/message-factory", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/message-factory.git", - "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/message-factory/zipball/4d8778e1c7d405cbb471574821c1ff5b68cc8f57", - "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57", - "shasum": "" - }, - "require": { - "php": ">=5.4", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "Factory interfaces for PSR-7 HTTP Message", - "homepage": "http://php-http.org", - "keywords": [ - "factory", - "http", - "message", - "stream", - "uri" - ], - "support": { - "issues": "https://github.com/php-http/message-factory/issues", - "source": "https://github.com/php-http/message-factory/tree/1.1.0" - }, - "time": "2023-04-14T14:16:17+00:00" - }, { "name": "phpcompatibility/php-compatibility", "version": "9.3.5", @@ -2009,16 +1965,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.14", + "version": "1.12.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d232901b09e67538e5c86a724be841bea5768a7c" + "reference": "0ca1c7bb55fca8fe6448f16fff0f311ccec960a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c", - "reference": "d232901b09e67538e5c86a724be841bea5768a7c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0ca1c7bb55fca8fe6448f16fff0f311ccec960a1", + "reference": "0ca1c7bb55fca8fe6448f16fff0f311ccec960a1", "shasum": "" }, "require": { @@ -2061,31 +2017,27 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2023-04-19T13:47:27+00:00" + "time": "2024-09-05T16:09:28+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "1.3.11", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "9e1b9de6d260461f6e99b6a8f2dbb0bbb98b579c" + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/9e1b9de6d260461f6e99b6a8f2dbb0bbb98b579c", - "reference": "9e1b9de6d260461f6e99b6a8f2dbb0bbb98b579c", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.11" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -2093,7 +2045,7 @@ "require-dev": { "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-strict-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.5.1", "phpunit/phpunit": "^9.5" }, "type": "phpstan-extension", @@ -2117,41 +2069,41 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.11" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" }, - "time": "2023-03-25T19:42:13+00:00" + "time": "2024-04-20T06:39:00+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.26", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -2160,7 +2112,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -2188,7 +2140,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -2196,7 +2149,7 @@ "type": "github" } ], - "time": "2023-03-06T12:58:08+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2417,157 +2370,533 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.21", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.5.0 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.21" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-09-19T10:50:18+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.5", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", + "react/socket": "^1.8", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Event-driven library for executing child processes with ReactPHP.", "keywords": [ - "timer" + "event-driven", + "process", + "reactphp" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.5" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2022-09-16T13:41:56+00:00" }, { - "name": "phpunit/phpunit", - "version": "9.6.7", + "name": "react/dns", + "version": "v1.13.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" }, - "bin": [ - "phpunit" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.6-dev" - } - }, "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] + "psr-4": { + "React\\Dns\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Async DNS resolver for ReactPHP", "keywords": [ - "phpunit", - "testing", - "xunit" + "async", + "dns", + "dns-resolver", + "reactphp" ], "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2023-04-14T08:58:40+00:00" + "time": "2024-06-13T14:18:03+00:00" }, { - "name": "psr/cache", - "version": "1.0.1", + "name": "react/event-loop", + "version": "v1.5.0", "source": { "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", "shasum": "" }, "require": { "php": ">=5.3.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Cache\\": "src/" + "React\\EventLoop\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2576,42 +2905,71 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Common interface for caching libraries", + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", "keywords": [ - "cache", - "psr", - "psr-6" + "asynchronous", + "event-loop" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" }, - "time": "2016-08-06T20:24:11+00:00" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" }, { - "name": "psr/container", - "version": "1.1.2", + "name": "react/promise", + "version": "v3.3.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { - "php": ">=7.4.0" + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Psr\\Container\\": "src/" + "React\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2620,51 +2978,75 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "promise", + "promises" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" }, - "time": "2021-11-05T16:50:12+00:00" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" }, { - "name": "psr/event-dispatcher", - "version": "1.0.0", + "name": "react/socket", + "version": "v1.16.0", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", "shasum": "" }, "require": { - "php": ">=7.2.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\EventDispatcher\\": "src/" + "React\\Socket\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2673,48 +3055,73 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Standard interfaces for event handling.", + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", "keywords": [ - "events", - "psr", - "psr-14" + "Connection", + "Socket", + "async", + "reactphp", + "stream" ], "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.16.0" }, - "time": "2019-01-08T18:20:26+00:00" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-07-26T10:38:09+00:00" }, { - "name": "psr/log", - "version": "1.1.4", + "name": "react/stream", + "version": "v1.4.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", "shasum": "" }, "require": { - "php": ">=5.3.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "React\\Stream\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2723,34 +3130,61 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", "keywords": [ - "log", - "psr", - "psr-3" + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" }, - "time": "2021-05-03T11:20:27+00:00" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" }, { "name": "sabberworm/php-css-parser", - "version": "8.4.0", + "version": "v8.6.0", "source": { "type": "git", - "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", - "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30" + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/e41d2140031d533348b2192a83f02d8dd8a71d30", - "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d2fb94a9641be84d79c7548c6d39bbebba6e9a70", + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70", "shasum": "" }, "require": { @@ -2758,13 +3192,17 @@ "php": ">=5.6.20" }, "require-dev": { - "codacy/coverage": "^1.4", - "phpunit/phpunit": "^4.8.36" + "phpunit/phpunit": "^5.7.27" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0.x-dev" + } + }, "autoload": { "psr-4": { "Sabberworm\\CSS\\": "src/" @@ -2777,6 +3215,14 @@ "authors": [ { "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" } ], "description": "Parser for CSS Files written in PHP", @@ -2787,23 +3233,23 @@ "stylesheet" ], "support": { - "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues", - "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0" + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.6.0" }, - "time": "2021-12-11T13:40:54+00:00" + "time": "2024-07-01T07:33:21+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { @@ -2838,7 +3284,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -2846,7 +3292,7 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", @@ -3035,20 +3481,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -3080,7 +3526,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -3088,20 +3534,20 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { @@ -3146,7 +3592,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -3154,7 +3600,7 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", @@ -3221,16 +3667,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { @@ -3286,7 +3732,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -3294,20 +3740,20 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { @@ -3350,7 +3796,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -3358,24 +3804,24 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -3407,7 +3853,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -3415,7 +3861,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -3594,16 +4040,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -3615,7 +4061,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3636,8 +4082,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -3645,7 +4090,7 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", @@ -3758,31 +4203,31 @@ }, { "name": "setasign/fpdi", - "version": "v2.3.7", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/Setasign/FPDI.git", - "reference": "bccc892d5fa1f48c43f8ba7db5ed4ba6f30c8c05" + "reference": "4b53852fde2734ec6a07e458a085db627c60eada" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDI/zipball/bccc892d5fa1f48c43f8ba7db5ed4ba6f30c8c05", - "reference": "bccc892d5fa1f48c43f8ba7db5ed4ba6f30c8c05", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/4b53852fde2734ec6a07e458a085db627c60eada", + "reference": "4b53852fde2734ec6a07e458a085db627c60eada", "shasum": "" }, "require": { "ext-zlib": "*", - "php": "^5.6 || ^7.0 || ^8.0" + "php": "^7.1 || ^8.0" }, "conflict": { "setasign/tfpdf": "<1.31" }, "require-dev": { - "phpunit/phpunit": "~5.7", - "setasign/fpdf": "~1.8", - "setasign/tfpdf": "1.31", + "phpunit/phpunit": "^7", + "setasign/fpdf": "~1.8.6", + "setasign/tfpdf": "~1.33", "squizlabs/php_codesniffer": "^3.5", - "tecnickcom/tcpdf": "~6.2" + "tecnickcom/tcpdf": "^6.8" }, "suggest": { "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured." @@ -3818,7 +4263,7 @@ ], "support": { "issues": "https://github.com/Setasign/FPDI/issues", - "source": "https://github.com/Setasign/FPDI/tree/v2.3.7" + "source": "https://github.com/Setasign/FPDI/tree/v2.6.4" }, "funding": [ { @@ -3826,20 +4271,20 @@ "type": "tidelift" } ], - "time": "2023-02-09T10:38:43+00:00" + "time": "2025-08-05T09:57:14+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.2", + "version": "3.10.2", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", "shasum": "" }, "require": { @@ -3849,11 +4294,11 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "bin/phpcbf", + "bin/phpcs" ], "type": "library", "extra": { @@ -3868,35 +4313,58 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, - "time": "2023-02-22T23:07:41+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-07-21T23:26:44+00:00" }, { "name": "symfony/console", - "version": "v5.4.23", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "90f21e27d0d88ce38720556dd164d4a1e4c3934c" + "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/90f21e27d0d88ce38720556dd164d4a1e4c3934c", - "reference": "90f21e27d0d88ce38720556dd164d4a1e4c3934c", + "url": "https://api.github.com/repos/symfony/console/zipball/e86f8554de667c16dde8aeb89a3990cfde924df9", + "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9", "shasum": "" }, "require": { @@ -3966,7 +4434,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.23" + "source": "https://github.com/symfony/console/tree/v5.4.43" }, "funding": [ { @@ -3982,20 +4450,20 @@ "type": "tidelift" } ], - "time": "2023-04-24T18:47:29+00:00" + "time": "2024-08-13T16:31:56+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + "reference": "80d075412b557d41002320b96a096ca65aa2c98d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/80d075412b557d41002320b96a096ca65aa2c98d", + "reference": "80d075412b557d41002320b96a096ca65aa2c98d", "shasum": "" }, "require": { @@ -4033,7 +4501,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.3" }, "funding": [ { @@ -4049,20 +4517,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2023-01-24T14:02:46+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.4.22", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f" + "reference": "a54e2a8a114065f31020d6a89ede83e34c3b27a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1df20e45d56da29a4b1d8259dd6e950acbf1b13f", - "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a54e2a8a114065f31020d6a89ede83e34c3b27a4", + "reference": "a54e2a8a114065f31020d6a89ede83e34c3b27a4", "shasum": "" }, "require": { @@ -4118,7 +4586,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.22" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.40" }, "funding": [ { @@ -4134,20 +4602,20 @@ "type": "tidelift" } ], - "time": "2023-03-17T11:31:58+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" + "reference": "540f4c73e87fd0c71ca44a6aa305d024ac68cb73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/540f4c73e87fd0c71ca44a6aa305d024ac68cb73", + "reference": "540f4c73e87fd0c71ca44a6aa305d024ac68cb73", "shasum": "" }, "require": { @@ -4197,7 +4665,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.3" }, "funding": [ { @@ -4213,20 +4681,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2024-01-23T13:51:25+00:00" }, { "name": "symfony/filesystem", - "version": "v5.4.23", + "version": "v5.4.41", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5" + "reference": "6d29dd9340b372fa603f04e6df4dd76bb808591e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5", - "reference": "b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/6d29dd9340b372fa603f04e6df4dd76bb808591e", + "reference": "6d29dd9340b372fa603f04e6df4dd76bb808591e", "shasum": "" }, "require": { @@ -4235,6 +4703,9 @@ "symfony/polyfill-mbstring": "~1.8", "symfony/polyfill-php80": "^1.16" }, + "require-dev": { + "symfony/process": "^5.4|^6.4" + }, "type": "library", "autoload": { "psr-4": { @@ -4261,7 +4732,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.23" + "source": "https://github.com/symfony/filesystem/tree/v5.4.41" }, "funding": [ { @@ -4277,20 +4748,20 @@ "type": "tidelift" } ], - "time": "2023-03-02T11:38:35+00:00" + "time": "2024-06-28T09:36:24+00:00" }, { "name": "symfony/finder", - "version": "v5.4.21", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "078e9a5e1871fcfe6a5ce421b539344c21afef19" + "reference": "ae25a9145a900764158d439653d5630191155ca0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/078e9a5e1871fcfe6a5ce421b539344c21afef19", - "reference": "078e9a5e1871fcfe6a5ce421b539344c21afef19", + "url": "https://api.github.com/repos/symfony/finder/zipball/ae25a9145a900764158d439653d5630191155ca0", + "reference": "ae25a9145a900764158d439653d5630191155ca0", "shasum": "" }, "require": { @@ -4324,7 +4795,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.21" + "source": "https://github.com/symfony/finder/tree/v5.4.43" }, "funding": [ { @@ -4340,20 +4811,20 @@ "type": "tidelift" } ], - "time": "2023-02-16T09:33:00+00:00" + "time": "2024-08-13T14:03:51+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.4.21", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9" + "reference": "bd1afbde6613a8d6b956115e0e14b196191fd0c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", - "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/bd1afbde6613a8d6b956115e0e14b196191fd0c4", + "reference": "bd1afbde6613a8d6b956115e0e14b196191fd0c4", "shasum": "" }, "require": { @@ -4393,7 +4864,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.4.21" + "source": "https://github.com/symfony/options-resolver/tree/v5.4.40" }, "funding": [ { @@ -4409,24 +4880,24 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -4436,9 +4907,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -4475,7 +4943,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -4491,20 +4959,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", "shasum": "" }, "require": { @@ -4515,9 +4983,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -4556,7 +5021,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" }, "funding": [ { @@ -4572,20 +5037,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", "shasum": "" }, "require": { @@ -4596,9 +5061,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -4640,7 +5102,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" }, "funding": [ { @@ -4656,20 +5118,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" + "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", + "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", "shasum": "" }, "require": { @@ -4677,9 +5139,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -4719,7 +5178,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" }, "funding": [ { @@ -4735,20 +5194,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { @@ -4756,9 +5215,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -4802,7 +5258,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" }, "funding": [ { @@ -4818,20 +5274,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", "shasum": "" }, "require": { @@ -4839,9 +5295,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -4881,7 +5334,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" }, "funding": [ { @@ -4897,20 +5350,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/process", - "version": "v5.4.23", + "version": "v5.4.46", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "4b842fc4b61609e0a155a114082bd94e31e98287" + "reference": "01906871cb9b5e3cf872863b91aba4ec9767daf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/4b842fc4b61609e0a155a114082bd94e31e98287", - "reference": "4b842fc4b61609e0a155a114082bd94e31e98287", + "url": "https://api.github.com/repos/symfony/process/zipball/01906871cb9b5e3cf872863b91aba4ec9767daf4", + "reference": "01906871cb9b5e3cf872863b91aba4ec9767daf4", "shasum": "" }, "require": { @@ -4943,7 +5396,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.23" + "source": "https://github.com/symfony/process/tree/v5.4.46" }, "funding": [ { @@ -4959,20 +5412,20 @@ "type": "tidelift" } ], - "time": "2023-04-18T13:50:24+00:00" + "time": "2024-11-06T09:18:28+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", "shasum": "" }, "require": { @@ -5026,7 +5479,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" }, "funding": [ { @@ -5042,20 +5495,20 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:17:29+00:00" + "time": "2023-04-21T15:04:16+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.4.21", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "f83692cd869a6f2391691d40a01e8acb89e76fee" + "reference": "0e9daf3b7c805c747638b2cc48f1649e594f9625" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/f83692cd869a6f2391691d40a01e8acb89e76fee", - "reference": "f83692cd869a6f2391691d40a01e8acb89e76fee", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/0e9daf3b7c805c747638b2cc48f1649e594f9625", + "reference": "0e9daf3b7c805c747638b2cc48f1649e594f9625", "shasum": "" }, "require": { @@ -5088,7 +5541,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.21" + "source": "https://github.com/symfony/stopwatch/tree/v5.4.40" }, "funding": [ { @@ -5104,20 +5557,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "symfony/string", - "version": "v5.4.22", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62" + "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", - "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "url": "https://api.github.com/repos/symfony/string/zipball/8be1d484951ff5ca995eaf8edcbcb8b9a5888450", + "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450", "shasum": "" }, "require": { @@ -5174,7 +5627,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.22" + "source": "https://github.com/symfony/string/tree/v5.4.43" }, "funding": [ { @@ -5190,24 +5643,25 @@ "type": "tidelift" } ], - "time": "2023-03-14T06:11:53+00:00" + "time": "2024-08-01T10:24:28+00:00" }, { "name": "tecnickcom/tcpdf", - "version": "6.6.2", + "version": "6.8.0", "source": { "type": "git", "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "e3cffc9bcbc76e89e167e9eb0bbda0cab7518459" + "reference": "14ffa0e308f5634aa2489568b4b90b24073b6731" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/e3cffc9bcbc76e89e167e9eb0bbda0cab7518459", - "reference": "e3cffc9bcbc76e89e167e9eb0bbda0cab7518459", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/14ffa0e308f5634aa2489568b4b90b24073b6731", + "reference": "14ffa0e308f5634aa2489568b4b90b24073b6731", "shasum": "" }, "require": { - "php": ">=5.3.0" + "ext-curl": "*", + "php": ">=7.1.0" }, "type": "library", "autoload": { @@ -5232,7 +5686,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0-only" + "LGPL-3.0-or-later" ], "authors": [ { @@ -5254,7 +5708,7 @@ ], "support": { "issues": "https://github.com/tecnickcom/TCPDF/issues", - "source": "https://github.com/tecnickcom/TCPDF/tree/6.6.2" + "source": "https://github.com/tecnickcom/TCPDF/tree/6.8.0" }, "funding": [ { @@ -5262,20 +5716,20 @@ "type": "custom" } ], - "time": "2022-12-17T10:28:59+00:00" + "time": "2024-12-23T13:34:57+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -5304,7 +5758,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -5312,7 +5766,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], @@ -5323,7 +5777,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.4 || ^8.0", + "php": ">=7.4.0 <8.5.0", "ext-ctype": "*", "ext-dom": "*", "ext-fileinfo": "*", @@ -5338,6 +5792,6 @@ "ext-zip": "*", "ext-zlib": "*" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/docs/topics/reading-and-writing-to-file.md b/docs/topics/reading-and-writing-to-file.md index 5e001e83c0..8645f6a195 100644 --- a/docs/topics/reading-and-writing-to-file.md +++ b/docs/topics/reading-and-writing-to-file.md @@ -298,7 +298,6 @@ versions of Microsoft Excel. **Excel 2003 XML limitations** Please note that Excel 2003 XML format has some limits regarding to styling cells and handling large spreadsheets via PHP. -Also, only files using charset UTF-8 are supported. ### \PhpOffice\PhpSpreadsheet\Reader\Xml @@ -718,7 +717,7 @@ extension. **HTML limitations** Please note that HTML file format has some limits regarding to styling cells, number formatting, ... -Also, only files using charset UTF-8 are supported. +Also, only files using ASCII characters, or charset UTF-8 (without BOM) are supported. ### \PhpOffice\PhpSpreadsheet\Reader\Html @@ -733,7 +732,7 @@ $spreadsheet = $reader->load("05featuredemo.html"); ``` **HTML limitations** Please note that HTML reader is still experimental -and does not yet support merged cells or nested tables cleanly +and does not yet support merged cells or nested tables cleanly. ### \PhpOffice\PhpSpreadsheet\Writer\Html diff --git a/docs/topics/reading-files.md b/docs/topics/reading-files.md index 767052812c..27b6bb60c6 100644 --- a/docs/topics/reading-files.md +++ b/docs/topics/reading-files.md @@ -303,7 +303,7 @@ class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter public function readCell($columnAddress, $row, $worksheetName = '') { // Read rows 1 to 7 and columns A to E only if ($row >= 1 && $row <= 7) { - if (in_array($column,range('A','E'))) { + if (in_array($columnAddress,range('A','E'))) { return true; } } @@ -348,7 +348,7 @@ class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter public function readCell($columnAddress, $row, $worksheetName = '') { // Only read the rows and columns that were configured if ($row >= $this->startRow && $row <= $this->endRow) { - if (in_array($column,$this->columns)) { + if (in_array($columnAddress,$this->columns)) { return true; } } diff --git a/docs/topics/settings.md b/docs/topics/settings.md index d28a9996dd..4463ceebb3 100644 --- a/docs/topics/settings.md +++ b/docs/topics/settings.md @@ -43,20 +43,3 @@ More details of the features available once a locale has been set, including a list of the languages and locales currently supported, can be found in [Locale Settings for Formulae](./recipes.md#locale-settings-for-formulae). - -## HTTP client - -In order to use the `WEBSERVICE` function in formulae, you must configure an -HTTP client. Assuming you chose Guzzle 7, this can be done like: - - -```php -use GuzzleHttp\Client; -use Http\Factory\Guzzle\RequestFactory; -use PhpOffice\PhpSpreadsheet\Settings; - -$client = new Client(); -$requestFactory = new RequestFactory(); - -Settings::setHttpClient($client, $requestFactory); -``` diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index eff03914e8..aa7402313d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,5 +1,70 @@ parameters: ignoreErrors: + - + message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|numeric\\-string, 2\\: '\\+', 3\\?\\: ''\\|numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|numeric\\-string, 2\\: ',', 3\\?\\: ''\\|numeric\\-string\\}\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|numeric\\-string, 2\\: '\\-', 3\\?\\: ''\\|numeric\\-string\\}\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Offset 2 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Offset 3 does not exist on array\\{0\\?\\: string, 1\\: ''\\|numeric\\-string, 2\\: '\\-', 3\\?\\: ''\\|numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Offset 3 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|numeric\\-string, 2\\: ',', 3\\?\\: ''\\|numeric\\-string\\}\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Offset 3 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|numeric\\-string, 2\\: '\\-', 3\\?\\: ''\\|numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Offset 8 on array\\{string, string, string, string, string, string, non\\-empty\\-string, numeric\\-string\\} in isset\\(\\) does not exist\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Strict comparison using \\=\\=\\= between mixed and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Binary operation \"%%\" between int\\|string and 100 results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php + + - + message: "#^Binary operation \"%%\" between int\\|string and 4 results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php + + - + message: "#^Binary operation \"%%\" between int\\|string and 400 results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php + + - + message: "#^Binary operation \"\\-\" between 1 and array\\|float\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/Erf.php + - message: "#^Cannot call method getTokenSubType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#" count: 4 @@ -24,3 +89,238 @@ parameters: message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken and null will always evaluate to false\\.$#" count: 1 path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: non\\-falsy\\-string, 2\\?\\: string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Offset 2 does not exist on array\\{0\\?\\: string, 1\\?\\: non\\-falsy\\-string, 2\\?\\: string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Offset 2 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Information/Value.php + + - + message: "#^Offset 6 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Information/Value.php + + - + message: "#^Offset 7 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Information/Value.php + + - + message: "#^Offset 3 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Formula.php + + - + message: "#^Offset 6 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Formula.php + + - + message: "#^Offset 7 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Formula.php + + - + message: "#^Binary operation \"/\" between float\\|int and float\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Deviations.php + + - + message: "#^Binary operation \"\\-\" between 1 and array\\|float\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Binary operation \"\\-\" between 1 and array\\|float\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php + + - + message: "#^Offset 'col' does not exist on array\\{0\\?\\: string, col\\?\\: non\\-empty\\-string, 1\\?\\: non\\-empty\\-string, row\\?\\: non\\-empty\\-string, 2\\?\\: non\\-empty\\-string\\}\\.$#" + count: 2 + path: src/PhpSpreadsheet/Cell/AddressHelper.php + + - + message: "#^Offset 'row' does not exist on array\\{0\\?\\: string, col\\?\\: non\\-empty\\-string, 1\\?\\: non\\-empty\\-string, row\\?\\: non\\-empty\\-string, 2\\?\\: non\\-empty\\-string\\}\\.$#" + count: 2 + path: src/PhpSpreadsheet/Cell/AddressHelper.php + + - + message: "#^Parameter \\#1 \\$num of function dechex expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Offset 'size' does not exist on array\\{0\\?\\: string, size\\?\\: non\\-empty\\-string, 1\\?\\: non\\-empty\\-string, unit\\?\\: non\\-falsy\\-string, 2\\?\\: non\\-falsy\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Size.php + + - + message: "#^Parameter \\#2 \\$length of function fgetcsv expects int\\<0, max\\>\\|null, int\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Csv.php + + - + message: "#^Parameter \\#1 \\$namespace of method DOMDocument\\:\\:getElementsByTagNameNS\\(\\) expects string, string\\|null given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#1 \\$namespace of method DOMElement\\:\\:getElementsByTagNameNS\\(\\) expects string, string\\|null given\\.$#" + count: 7 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#2 \\$tableNs of class PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\AutoFilter constructor expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#2 \\$tableNs of class PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\DefinedNames constructor expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#2 \\$tableNs of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:processMergedCells\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#3 \\$configNs of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:lookForActiveSheet\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#3 \\$configNs of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:lookForSelectedCells\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$officeNs \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$stylesFo \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$stylesNs \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$tableNs \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Parameter \\#1 \\$column of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getColumnDimension\\(\\) expects string, \\(float\\|int\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Slk.php + + - + message: "#^Offset 4 does not exist on array\\{0\\: string, 1\\: non\\-empty\\-string, 2\\: numeric\\-string, 3\\: string, 4\\?\\: string, 5\\?\\: numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Offset 5 does not exist on array\\{0\\: string, 1\\: non\\-empty\\-string, 2\\: numeric\\-string, 3\\: string, 4\\?\\: string, 5\\?\\: numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Variable \\$column in empty\\(\\) always exists and is not falsy\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: non\\-falsy\\-string, 2\\?\\: non\\-empty\\-string\\|numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php + + - + message: "#^Offset 2 does not exist on array\\{0\\?\\: string, 1\\?\\: non\\-falsy\\-string, 2\\?\\: non\\-empty\\-string\\|numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php + + - + message: "#^Variable \\$language on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.php + + - + message: "#^Offset 'mime' on array\\{0\\: int\\<0, max\\>, 1\\: int\\<0, max\\>, 2\\: int, 3\\: string, mime\\: string, channels\\?\\: int, bits\\?\\: int\\} on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/MemoryDrawing.php + + - + message: "#^Variable \\$rgb in empty\\(\\) always exists and is not falsy\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/MemoryDrawing.php + + - + message: "#^Parameter \\#1 \\$filename of function fopen expects string, resource\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/BaseWriter.php + + - + message: "#^Parameter \\#1 \\$url of function parse_url expects string, resource\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/BaseWriter.php + + - + message: "#^Offset 'mime' does not exist on array\\{\\}\\|array\\{0\\: int\\<0, max\\>, 1\\: int\\<0, max\\>, 2\\: int, 3\\: string, mime\\: string, channels\\?\\: int, bits\\?\\: int\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Offset 0 does not exist on array\\{\\}\\|array\\{0\\: int\\<0, max\\>, 1\\: int\\<0, max\\>, 2\\: int, 3\\: string, mime\\: string, channels\\?\\: int, bits\\?\\: int\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Offset 1 does not exist on array\\{\\}\\|array\\{0\\: int\\<0, max\\>, 1\\: int\\<0, max\\>, 2\\: int, 3\\: string, mime\\: string, channels\\?\\: int, bits\\?\\: int\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Variable \\$column in empty\\(\\) always exists and is not falsy\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/Formula.php + + - + message: "#^Variable \\$column in empty\\(\\) always exists and is not falsy\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php + + - + message: "#^Offset 2 on array\\{0\\: int\\<0, max\\>, 1\\: int\\<0, max\\>, 2\\: int, 3\\: string, mime\\: string, channels\\?\\: int, bits\\?\\: int\\} on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#2 \\$length of function fread expects int\\<1, max\\>, int\\<0, max\\> given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Comparison operation \"\\>\\=\" between int\\<5, 7\\> and 3 is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php + + - + message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: numeric\\-string\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php diff --git a/phpstan-conditional.php b/phpstan-conditional.php index 9b1150b34c..fa093b31ff 100644 --- a/phpstan-conditional.php +++ b/phpstan-conditional.php @@ -49,28 +49,28 @@ //]; // Erroneous analysis by Phpstan before PHP8 - mb_strlen does not return false $config['parameters']['ignoreErrors'][] = [ - 'message' => '#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:countCharacters\\(\\) should return int but returns int(<0, max>)?\\|false\\.$#', + 'message' => '#^Method PhpOffice\\\PhpSpreadsheet\\\Shared\\\StringHelper\:\:countCharacters\(\) should return int but returns int(<0, max>)?\|false\.$#', 'path' => __DIR__ . '/src/PhpSpreadsheet/Shared/StringHelper.php', 'count' => 1, ]; // New with Phpstan 1.9.2 for Php7 only $config['parameters']['ignoreErrors'][] = [ - 'message' => '#^Parameter \\#2 \\.\\.\\.\\$args of function array_merge expects array, array\\|false given.$#', + 'message' => '#^Parameter \#2 \.\.\.\$args of function array_merge expects array, array\|false given.$#', 'path' => __DIR__ . '/src/PhpSpreadsheet/Calculation/LookupRef/Sort.php', 'count' => 1, ]; $config['parameters']['ignoreErrors'][] = [ - 'message' => '#^Parameter \\#1 \\$input of function array_chunk expects array, array\\|false given.$#', + 'message' => '#^Parameter \#1 \$input of function array_chunk expects array, array\|false given.$#', 'path' => __DIR__ . '/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php', 'count' => 1, ]; $config['parameters']['ignoreErrors'][] = [ - 'message' => '#^Parameter \\#2 \\$array of function array_map expects array, array\\|false given.$#', + 'message' => '#^Parameter \#2 \$array of function array_map expects array, array\|false given.$#', 'path' => __DIR__ . '/src/PhpSpreadsheet/Calculation/MathTrig/Random.php', 'count' => 1, ]; $config['parameters']['ignoreErrors'][] = [ - 'message' => '#^Parameter \\#2 \\.\\.\\.\\$args of function array_merge expects array, array\\|false given.$#', + 'message' => '#^Parameter \#2 \.\.\.\$args of function array_merge expects array, array\|false given.$#', 'path' => __DIR__ . '/src/PhpSpreadsheet/Calculation/TextData/Text.php', 'count' => 1, ]; diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 30bd6c2f7b..91329377fe 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -3,6 +3,7 @@ includes: - phpstan-conditional.php - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon + - vendor/composer/pcre/extension.neon parameters: level: 8 @@ -12,10 +13,14 @@ parameters: excludePaths: - src/PhpSpreadsheet/Chart/Renderer/JpGraph.php - src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php + - src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php + - src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php + - src/PhpSpreadsheet/Writer/ZipStream2.php + - src/PhpSpreadsheet/Writer/ZipStream3.php parallel: processTimeout: 300.0 - checkMissingIterableValueType: false ignoreErrors: + - identifier: missingType.iterableValue - '~^Parameter \#1 \$im(age)? of function (imagedestroy|imageistruecolor|imagealphablending|imagesavealpha|imagecolortransparent|imagecolorsforindex|imagesavealpha|imagesx|imagesy|imagepng) expects (GdImage|resource), GdImage\|resource given\.$~' - '~^Parameter \#2 \$src_im(age)? of function imagecopy expects (GdImage|resource), GdImage\|resource given\.$~' # Accept a bit anything for assert methods diff --git a/samples/Basic/45_Quadratic_equation_solver.php b/samples/Basic/45_Quadratic_equation_solver.php index 8e798da2f3..d681a21092 100644 --- a/samples/Basic/45_Quadratic_equation_solver.php +++ b/samples/Basic/45_Quadratic_equation_solver.php @@ -14,7 +14,7 @@ } ?>
- Enter the coefficients for the Ax2 + Bx + C = 0 + Enter the coefficients for Ax2 + Bx + C = 0 ', $html); + $spreadsheet->disconnectWorksheets(); + } + + public function testControlCharacter(): void + { + $reader = new XmlReader(); + $infile = 'tests/data/Reader/Xml/sec-w24f.dontuse'; + $spreadsheet = $reader->load($infile); + $writer = new HtmlWriter($spreadsheet); + $html = $writer->generateHtmlAll(); + self::assertStringContainsString('', $html); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Html/ExtendForChartsAndImagesTest.php b/tests/PhpSpreadsheetTests/Writer/Html/ExtendForChartsAndImagesTest.php index e3d23230c1..f2db5c5904 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/ExtendForChartsAndImagesTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/ExtendForChartsAndImagesTest.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Html; +use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Writer\Html; @@ -57,7 +58,9 @@ public function testSheetWithImageBelowData(): void // Add a drawing to the worksheet $drawing = new Drawing(); - $drawing->setPath('foo.png', false); + $path = 'tests/data/Writer/XLSX/blue_square.png'; + $drawing->setPath($path); + self::assertSame($path, $drawing->getPath()); $drawing->setCoordinates('A5'); $drawing->setWorksheet($sheet); @@ -72,13 +75,51 @@ public function testSheetWithImageRightOfData(): void // Add a drawing to the worksheet $drawing = new Drawing(); - $drawing->setPath('foo.png', false); + $path = 'tests/data/Writer/XLSX/blue_square.png'; + $drawing->setPath($path); + self::assertSame($path, $drawing->getPath()); $drawing->setCoordinates('E1'); $drawing->setWorksheet($sheet); $this->assertMaxColumnAndMaxRow($spreadsheet, 5, 3); } + public function testSheetWithBadImageRightOfData(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('B3', 'foo'); + + // Add a drawing to the worksheet + $drawing = new Drawing(); + $path = 'tests/data/Writer/XLSX/xblue_square.png'; + $drawing->setPath($path, false); + self::assertSame('', $drawing->getPath()); + $drawing->setCoordinates('E1'); + $drawing->setWorksheet($sheet); + + $this->assertMaxColumnAndMaxRow($spreadsheet, 2, 3); + } + + public function testSheetWithBadImageRightOfDataThrow(): void + { + $this->expectException(PhpSpreadsheetException::class); + $this->expectExceptionMessage('not found!'); + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('B3', 'foo'); + + // Add a drawing to the worksheet + $drawing = new Drawing(); + $path = 'tests/data/Writer/XLSX/xblue_square.png'; + $drawing->setPath($path); + self::assertSame('', $drawing->getPath()); + $drawing->setCoordinates('E1'); + $drawing->setWorksheet($sheet); + + $this->assertMaxColumnAndMaxRow($spreadsheet, 2, 3); + } + private function assertMaxColumnAndMaxRow(Spreadsheet $spreadsheet, int $expectedColumnCount, int $expectedRowCount): void { $writer = new Html($spreadsheet); diff --git a/tests/PhpSpreadsheetTests/Writer/Html/ImageEmbedTest.php b/tests/PhpSpreadsheetTests/Writer/Html/ImageEmbedTest.php new file mode 100644 index 0000000000..bc9e40ed4c --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Html/ImageEmbedTest.php @@ -0,0 +1,45 @@ +getActiveSheet(); + + $drawing = new Drawing(); + $drawing->setName('Not an image'); + $drawing->setDescription('Non-image'); + $drawing->setPath(__FILE__, false); + $drawing->setCoordinates('A1'); + $drawing->setCoordinates2('E4'); + $drawing->setWorksheet($sheet); + + $drawing = new Drawing(); + $drawing->setName('Blue Square'); + $drawing->setPath('tests/data/Writer/XLSX/blue_square.png'); + $drawing->setCoordinates('A5'); + $drawing->setCoordinates2('E8'); + $drawing->setWorksheet($sheet); + + $writer = new HtmlWriter($spreadsheet); + $writer->setEmbedImages(true); + $html = $writer->generateHTMLAll(); + self::assertSame(1, substr_count($html, 'disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Html/InvalidFileNameTest.php b/tests/PhpSpreadsheetTests/Writer/Html/InvalidFileNameTest.php index 36a40e965b..d87e21378f 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/InvalidFileNameTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/InvalidFileNameTest.php @@ -55,7 +55,7 @@ public function testEmptyTempdirNamePdf(): void public function testWinFileNames(): void { - self::assertEquals('file:///C:/temp/filename.xlsx', Html::winFileToUrl('C:\\temp\filename.xlsx')); + self::assertEquals('file:///C:/temp/filename.xlsx', Html::winFileToUrl('C:\temp\filename.xlsx')); self::assertEquals('/tmp/filename.xlsx', Html::winFileToUrl('/tmp/filename.xlsx')); self::assertEquals('a:bfile', Html::winFileToUrl('a:bfile')); } diff --git a/tests/PhpSpreadsheetTests/Writer/Html/MemoryDrawingOffsetTest.php b/tests/PhpSpreadsheetTests/Writer/Html/MemoryDrawingOffsetTest.php index 2cb2373c98..e367f174df 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/MemoryDrawingOffsetTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/MemoryDrawingOffsetTest.php @@ -36,7 +36,7 @@ public function testMemoryDrawingOffset(int $w, int $h, int $x, int $y): void unset($spreadsheet); } - public function dataProvider(): array + public static function dataProvider(): array { return [ [33, 19, 0, 20], diff --git a/tests/PhpSpreadsheetTests/Writer/Html/NavigationBadTitleTest.php b/tests/PhpSpreadsheetTests/Writer/Html/NavigationBadTitleTest.php new file mode 100644 index 0000000000..284fd9f4e5 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Html/NavigationBadTitleTest.php @@ -0,0 +1,35 @@ +getActiveSheet(); + $sheet->getCell('A1')->setValue(1); + $sheet2 = $spreadsheet->createSheet(); + $sheet2->setTitle(''); + $sheet2->getCell('A2')->setValue(2); + + $writer = new HtmlWriter($spreadsheet); + $writer->writeAllSheets(); + $html = $writer->generateHTMLAll(); + $expected = ''; + self::assertStringContainsString($expected, $html, 'appropriate characters are escaped'); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Html/NoJavascriptLinksTest.php b/tests/PhpSpreadsheetTests/Writer/Html/NoJavascriptLinksTest.php new file mode 100644 index 0000000000..7b1a633bc1 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Html/NoJavascriptLinksTest.php @@ -0,0 +1,33 @@ +getActiveSheet(); + $sheet->getCell('A1')->setValue('Click me'); + $hyperlink = new Hyperlink('http://www.example.com'); + $sheet->getCell('A1')->setHyperlink($hyperlink); + $sheet->getCell('A2')->setValue('JS link'); + $hyperlink2 = new Hyperlink('javascript:alert(\'hello1\')'); + $sheet->getCell('A2')->setHyperlink($hyperlink2); + $sheet->getCell('A3')->setValue('=HYPERLINK("javascript:alert(\'hello2\')", "jsfunc click")'); + + $writer = new Html($spreadsheet); + $html = $writer->generateHTMLAll(); + self::assertStringContainsString('', $html, 'http hyperlink retained'); + self::assertStringContainsString('', $html, 'javascript hyperlink dropped'); + self::assertStringContainsString('', $html, 'javascript hyperlink function dropped'); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Html/VisibilityTest.php b/tests/PhpSpreadsheetTests/Writer/Html/VisibilityTest.php index 1c1ffb0698..553c5e9a4c 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/VisibilityTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/VisibilityTest.php @@ -26,13 +26,13 @@ public function testVisibility1(): void $sheet->getRowDimension(2)->setVisible(false); $writer = new Html($spreadsheet); $html = $writer->generateHTMLAll(); - $reg = '/^\\s*table[.]sheet0 tr { display:none; visibility:hidden }\\s*$/m'; + $reg = '/^\s*table[.]sheet0 tr { display:none; visibility:hidden }\s*$/m'; $rowsrch = preg_match($reg, $html); self::assertEquals($rowsrch, 0); - $reg = '/^\\s*table[.]sheet0 tr[.]row1 { display:none; visibility:hidden }\\s*$/m'; + $reg = '/^\s*table[.]sheet0 tr[.]row1 { display:none; visibility:hidden }\s*$/m'; $rowsrch = preg_match($reg, $html); self::assertEquals($rowsrch, 1); - $reg = '/^\\s*table[.]sheet0 [.]column1 [{] display:none [}]\\s*$/m'; + $reg = '/^\s*table[.]sheet0 [.]column1 [{] display:none [}]\s*$/m'; $colsrch = preg_match($reg, $html); self::assertEquals($colsrch, 1); @@ -59,13 +59,13 @@ public function testVisibility2(): void $writer = new Html($spreadsheet); $html = $writer->generateHTMLAll(); - $reg = '/^\\s*table[.]sheet0 tr { height:15pt; display:none; visibility:hidden }\\s*$/m'; + $reg = '/^\s*table[.]sheet0 tr { height:15pt; display:none; visibility:hidden }\s*$/m'; $rowsrch = preg_match($reg, $html); self::assertEquals($rowsrch, 1); - $reg = '/^\\s*table[.]sheet0 tr[.]row1 { display:none; visibility:hidden }\\s*$/m'; + $reg = '/^\s*table[.]sheet0 tr[.]row1 { display:none; visibility:hidden }\s*$/m'; $rowsrch = preg_match($reg, $html); self::assertEquals($rowsrch, 0); - $reg = '/^\\s*table[.]sheet0 [.]column1 [{] display:none [}]\\s*$/m'; + $reg = '/^\s*table[.]sheet0 [.]column1 [{] display:none [}]\s*$/m'; $colsrch = preg_match($reg, $html); self::assertEquals($colsrch, 1); @@ -95,15 +95,15 @@ public function testDefaultRowHeight(): void $html = $writer->generateHTMLAll(); self::assertEquals(1, substr_count($html, 'height:20pt')); self::assertEquals(1, substr_count($html, 'height:25pt')); - $rowsrch = preg_match('/^\\s*table[.]sheet0 tr [{] height:20pt [}]\\s*$/m', $html); + $rowsrch = preg_match('/^\s*table[.]sheet0 tr [{] height:20pt [}]\s*$/m', $html); self::assertEquals(1, $rowsrch); - $rowsrch = preg_match('/^\\s*table[.]sheet0 tr[.]row1 [{] height:25pt [}]\\s*$/m', $html); + $rowsrch = preg_match('/^\s*table[.]sheet0 tr[.]row1 [{] height:25pt [}]\s*$/m', $html); self::assertEquals(1, $rowsrch); - $rowsrch = preg_match('/^\\s*td[.]style1, th[.]style1 [{].*text-decoration:line-through;.*[}]\\s*$/m', $html); + $rowsrch = preg_match('/^\s*td[.]style1, th[.]style1 [{].*text-decoration:line-through;.*[}]\s*$/m', $html); self::assertEquals(1, $rowsrch); - $rowsrch = preg_match('/^\\s*td[.]style2, th[.]style2 [{].*text-decoration:underline line-through;.*[}]\\s*$/m', $html); + $rowsrch = preg_match('/^\s*td[.]style2, th[.]style2 [{].*text-decoration:underline line-through;.*[}]\s*$/m', $html); self::assertEquals(1, $rowsrch); - $rowsrch = preg_match('/^\\s*td[.]style3, th[.]style3 [{].*text-decoration:underline;.*[}]\\s*$/m', $html); + $rowsrch = preg_match('/^\s*td[.]style3, th[.]style3 [{].*text-decoration:underline;.*[}]\s*$/m', $html); self::assertEquals(1, $rowsrch); $this->writeAndReload($spreadsheet, 'Html'); diff --git a/tests/PhpSpreadsheetTests/Writer/Html/XssVulnerabilityTest.php b/tests/PhpSpreadsheetTests/Writer/Html/XssVulnerabilityTest.php index d5396b2212..4aa2ba7961 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/XssVulnerabilityTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/XssVulnerabilityTest.php @@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Html; use PhpOffice\PhpSpreadsheetTests\Functional; class XssVulnerabilityTest extends Functional\AbstractFunctional @@ -53,7 +54,7 @@ public static function providerXssRichText(): array return [ 'script tag' => ["Hello, I am trying to your site"], 'javascript tag' => ["CLICK"], - 'with unicode' => ['CLICK'], + 'with unicode' => ['CLICK'], 'inline css' => ['
  • '], 'char value chevron' => ["\x3cscript src=http://www.example.com/malicious-code.js\x3e\x3c/script\x3e"], ]; @@ -87,4 +88,21 @@ public function testXssInComment($xssTextString): void // Ensure that executable js has been stripped from the comments self::assertStringNotContainsString($xssTextString, $verify); } + + public function testXssInFontName(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getCell('A1')->setValue('here'); + $used = 'Calibri + + +
  • @@ -47,7 +47,9 @@ log('Non-numeric input'); + } elseif ($_POST['A'] == 0) { $helper->log('The equation is not quadratic'); } else { // Calculate and Display the results @@ -59,8 +61,12 @@ $r1Formula = '=IMDIV(IMSUM(-' . $_POST['B'] . ',IMSQRT(' . $discriminant . ')),2 * ' . $_POST['A'] . ')'; $r2Formula = '=IF(' . $discriminant . '=0,"Only one root",IMDIV(IMSUB(-' . $_POST['B'] . ',IMSQRT(' . $discriminant . ')),2 * ' . $_POST['A'] . '))'; - $helper->log(Calculation::getInstance()->calculateFormula($r1Formula)); - $helper->log(Calculation::getInstance()->calculateFormula($r2Formula)); + /** @var string */ + $output = Calculation::getInstance()->calculateFormula($r1Formula); + $helper->log("$output"); + /** @var string */ + $output = Calculation::getInstance()->calculateFormula($r2Formula); + $helper->log("$output"); $callEndTime = microtime(true); $helper->logEndingNotes(); } diff --git a/samples/Calculations/Engineering/Convert-Online.php b/samples/Calculations/Engineering/Convert-Online.php new file mode 100644 index 0000000000..e5b53f4b88 --- /dev/null +++ b/samples/Calculations/Engineering/Convert-Online.php @@ -0,0 +1,93 @@ +isCli()) { + $helper->log('This example should only be run from a Web Browser' . PHP_EOL); + + return; +} + +$categories = ConvertUOM::getConversionCategories(); +$defaultCategory = $_POST['category'] ?? $categories[0]; +$units = []; +foreach ($categories as $category) { + $categoryUnits = ConvertUOM::getConversionCategoryUnitDetails($category)[$category]; + $categoryUnits = array_unique( + array_combine( + array_column($categoryUnits, 'unit'), + array_column($categoryUnits, 'description') + ) + ); + $units[$category] = $categoryUnits; +} + +?> + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + + +log('Quantity is not numeric'); + } elseif (isset($units[$_POST['category']][$fromUnit], $units[$_POST['category']][$toUnit])) { + /** @var float|string */ + $result = ConvertUOM::CONVERT($quantity, $fromUnit, $toUnit); + + $helper->log("{$quantity} {$units[$_POST['category']][$fromUnit]} is {$result} {$units[$_POST['category']][$toUnit]}"); + } else { + $helper->log('Please enter quantity and select From Unit and To Unit'); + } +} else { + $helper->log('Please enter quantity and select From Unit and To Unit'); +} diff --git a/samples/Chart/35_Chart_render33.php b/samples/Chart/35_Chart_render33.php index bd8075de47..97091fceec 100644 --- a/samples/Chart/35_Chart_render33.php +++ b/samples/Chart/35_Chart_render33.php @@ -24,10 +24,7 @@ $unresolvedErrors = []; } else { $unresolvedErrors = [ - // The following spreadsheet was created by 3rd party software, - // and doesn't include the data that usually accompanies a chart. - // That is good enough for Excel, but not for JpGraph. - '33_Chart_create_bar_stacked.xlsx', + //'33_Chart_create_bar_stacked.xlsx', // fixed with mitoteam/jpgraph 10.3 ]; } foreach ($inputFileNames as $inputFileName) { diff --git a/samples/Wizards/NumberFormat/Accounting.php b/samples/Wizards/NumberFormat/Accounting.php index 6e6296b239..7906988db2 100644 --- a/samples/Wizards/NumberFormat/Accounting.php +++ b/samples/Wizards/NumberFormat/Accounting.php @@ -85,6 +85,8 @@ $helper->log('The Sample Number Value must be numeric'); } elseif (!is_numeric($_POST['decimals']) || strpos($_POST['decimals'], '.') !== false || (int) $_POST['decimals'] < 0) { $helper->log('The Decimal Places value must be positive integer'); + } elseif (!in_array($_POST['currency'], array_keys($currencies), true)) { + $helper->log('Unrecognized currency symbol'); } else { try { $wizard = new Wizard\Accounting($_POST['currency'], $_POST['decimals'], isset($_POST['thousands']), (bool) $_POST['position'], (bool) $_POST['spacing']); @@ -93,12 +95,14 @@ $helper->log('
    Code:
    '); $helper->log('use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;'); $helper->log( - "\$mask = Wizard\\Accounting('{$_POST['currency']}', {$_POST['decimals']}, Wizard\\Number::" . + "\$wizard = new Wizard\\Accounting('{$_POST['currency']}', {$_POST['decimals']}, Wizard\\Number::" . (isset($_POST['thousands']) ? 'WITH_THOUSANDS_SEPARATOR' : 'WITHOUT_THOUSANDS_SEPARATOR') . ', Wizard\Currency::' . (((bool) $_POST['position']) ? 'LEADING_SYMBOL' : 'TRAILING_SYMBOL') . ', Wizard\Currency::' . (((bool) $_POST['spacing']) ? 'SYMBOL_WITH_SPACING' : 'SYMBOL_WITHOUT_SPACING') . - ');
    ' + ');' ); + $helper->log('$mask = $wizard->format();'); + $helper->log('
    '); $helper->log('echo (string) $mask;'); $helper->log('
    Mask:
    '); $helper->log($mask . '
    '); diff --git a/samples/Wizards/NumberFormat/Currency.php b/samples/Wizards/NumberFormat/Currency.php index b4373d5415..3749de1e15 100644 --- a/samples/Wizards/NumberFormat/Currency.php +++ b/samples/Wizards/NumberFormat/Currency.php @@ -85,6 +85,8 @@ $helper->log('The Sample Number Value must be numeric'); } elseif (!is_numeric($_POST['decimals']) || strpos($_POST['decimals'], '.') !== false || (int) $_POST['decimals'] < 0) { $helper->log('The Decimal Places value must be positive integer'); + } elseif (!in_array($_POST['currency'], array_keys($currencies), true)) { + $helper->log('Unrecognized currency symbol'); } else { try { $wizard = new Wizard\Currency($_POST['currency'], $_POST['decimals'], isset($_POST['thousands']), (bool) $_POST['position'], (bool) $_POST['spacing']); @@ -93,12 +95,14 @@ $helper->log('
    Code:
    '); $helper->log('use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;'); $helper->log( - "\$mask = Wizard\\Currency('{$_POST['currency']}', {$_POST['decimals']}, Wizard\\Number::" . + "\$wizard = new Wizard\\Currency('{$_POST['currency']}', {$_POST['decimals']}, Wizard\\Number::" . (isset($_POST['thousands']) ? 'WITH_THOUSANDS_SEPARATOR' : 'WITHOUT_THOUSANDS_SEPARATOR') . ', Wizard\Currency::' . (((bool) $_POST['position']) ? 'LEADING_SYMBOL' : 'TRAILING_SYMBOL') . ', Wizard\Currency::' . (((bool) $_POST['spacing']) ? 'SYMBOL_WITH_SPACING' : 'SYMBOL_WITHOUT_SPACING') . - ');
    ' + ');' ); + $helper->log('$mask = $wizard->format();'); + $helper->log('
    '); $helper->log('echo (string) $mask;'); $helper->log('
    Mask:
    '); $helper->log($mask . '
    '); diff --git "a/samples/images/\343\202\265\343\203\263\343\203\225\343\202\232\343\203\253.png" "b/samples/images/\343\202\265\343\203\263\343\203\225\343\202\232\343\203\253.png" new file mode 100644 index 0000000000..4bed8fc149 Binary files /dev/null and "b/samples/images/\343\202\265\343\203\263\343\203\225\343\202\232\343\203\253.png" differ diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index c88656b4a0..ed19e3d300 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -46,7 +46,7 @@ class Calculation // Defined Names: Named Range of cells, or Named Formulae const CALCULATION_REGEXP_DEFINEDNAME = '((([^\s,!&%^\/\*\+<>=-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?([_\p{L}][_\p{L}\p{N}\.]*)'; // Structured Reference (Fully Qualified and Unqualified) - const CALCULATION_REGEXP_STRUCTURED_REFERENCE = '([\p{L}_\\\\][\p{L}\p{N}\._]+)?(\[(?:[^\d\]+-])?)'; + const CALCULATION_REGEXP_STRUCTURED_REFERENCE = '([\p{L}_\\\][\p{L}\p{N}\._]+)?(\[(?:[^\d\]+-])?)'; // Error const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?'; @@ -152,13 +152,6 @@ class Calculation */ public $formulaError; - /** - * Reference Helper. - * - * @var ReferenceHelper - */ - private static $referenceHelper; - /** * An array of the nested cell references accessed by the calculation engine, used for the debug log. * @@ -1494,8 +1487,8 @@ public static function getExcelConstants(string $key) ], 'INDEX' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef\Matrix::class, 'index'], - 'argumentCount' => '2-4', + 'functionCall' => [Kasko::class, 'INDEX'], + 'argumentCount' => '1-4', ], 'INDIRECT' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, @@ -2246,7 +2239,7 @@ public static function getExcelConstants(string $key) ], 'ROUND' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig\Round::class, 'round'], + 'functionCall' => [Kasko::class, 'ROUND'], 'argumentCount' => '2', ], 'ROUNDBAHTDOWN' => [ @@ -2796,6 +2789,7 @@ public static function getExcelConstants(string $key) 'category' => Category::CATEGORY_WEB, 'functionCall' => [Web\Service::class, 'webService'], 'argumentCount' => '1', + 'passCellReference' => true, ], 'WEEKDAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, @@ -2925,7 +2919,6 @@ public function __construct(?Spreadsheet $spreadsheet = null) $this->cyclicReferenceStack = new CyclicReferenceStack(); $this->debugLog = new Logger($this->cyclicReferenceStack); $this->branchPruner = new BranchPruner($this->branchPruningEnabled); - self::$referenceHelper = ReferenceHelper::getInstance(); } private static function loadLocales(): void @@ -5623,7 +5616,7 @@ public function getImplementedFunctionNames() private function addDefaultArgumentValues(array $functionCall, array $args, array $emptyArguments): array { - $reflector = new ReflectionMethod(implode('::', $functionCall)); + $reflector = new ReflectionMethod($functionCall[0], $functionCall[1]); $methodArguments = $reflector->getParameters(); if (count($methodArguments) > 0) { @@ -5730,11 +5723,14 @@ private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksh $recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate(); // Adjust relative references in ranges and formulae so that we execute the calculation for the correct rows and columns - $definedNameValue = self::$referenceHelper->updateFormulaReferencesAnyWorksheet( - $definedNameValue, - Coordinate::columnIndexFromString($cell->getColumn()) - 1, - $cell->getRow() - 1 - ); + $definedNameValue = ReferenceHelper::getInstance() + ->updateFormulaReferencesAnyWorksheet( + $definedNameValue, + Coordinate::columnIndexFromString( + $cell->getColumn() + ) - 1, + $cell->getRow() - 1 + ); $this->debugLog->writeDebugLog('Value adjusted for relative references is %s', $definedNameValue); diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php index 9a9870d91d..e9e1a9b6af 100644 --- a/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php @@ -45,6 +45,11 @@ public static function fromString($dateValue) return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue); } + // try to parse as date iff there is at least one digit + if (is_string($dateValue) && preg_match('/\d/', $dateValue) !== 1) { + return ExcelError::VALUE(); + } + $dti = new DateTimeImmutable(); $baseYear = SharedDateHelper::getExcelCalendar(); $dateValue = trim($dateValue ?? '', '"'); diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php index bb9036f782..17629ae871 100644 --- a/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; +use Composer\Pcre\Preg; use Datetime; use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; use PhpOffice\PhpSpreadsheet\Calculation\Functions; @@ -12,6 +13,19 @@ class TimeValue { use ArrayEnabled; + private const EXTRACT_TIME = '/\b' + . '(\d+)' // match[1] - hour + . '(:' // start of match[2] (rest of string) - colon + . '(\d+' // start of match[3] - minute + . '(:\d+' // start of match[4] - colon and seconds + . '([.]\d+)?' // match[5] - optional decimal point followed by fractional seconds + . ')?' // end of match[4], which is optional + . ')' // end of match 3 + // Excel does not require 'm' to trail 'a' or 'p'; Php does + . '(\s*(a|p))?' // match[6] optional whitespace followed by optional match[7] a or p + . ')' // end of match[2] + . '/i'; + /** * TIMEVALUE. * @@ -42,13 +56,21 @@ public static function fromString($timeValue) return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue); } - $timeValue = trim($timeValue ?? '', '"'); - $timeValue = str_replace(['/', '.'], '-', $timeValue); + // try to parse as time iff there is at least one digit + if (is_string($timeValue) && preg_match('/\d/', $timeValue) !== 1) { + return ExcelError::VALUE(); + } - $arraySplit = preg_split('/[\/:\-\s]/', $timeValue) ?: []; - if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) { - $arraySplit[0] = ($arraySplit[0] % 24); - $timeValue = implode(':', $arraySplit); + $timeValue = trim($timeValue ?? '', '"'); + if (Preg::isMatch(self::EXTRACT_TIME, $timeValue, $matches)) { + if (empty($matches[6])) { // am/pm + $hour = (int) $matches[1]; + $timeValue = ($hour % 24) . $matches[2]; + } elseif ($matches[6] === $matches[7]) { // Excel wants space before am/pm + return ExcelError::VALUE(); + } else { + $timeValue = $matches[0] . 'm'; + } } $PHPDateArray = Helpers::dateParse($timeValue); diff --git a/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php b/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php index 3e88ece550..331fa448be 100644 --- a/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php +++ b/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php @@ -48,9 +48,9 @@ public static function convertToNumberIfFormatted(string &$operand): bool */ public static function convertToNumberIfNumeric(string &$operand): bool { - $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator()); + $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); $value = preg_replace(['/(\d)' . $thousandsSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1$2', '$1$2'], trim($operand)); - $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator()); + $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/'); $value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? ''); if (is_numeric($value)) { @@ -90,9 +90,9 @@ public static function convertToNumberIfFraction(string &$operand): bool */ public static function convertToNumberIfPercent(string &$operand): bool { - $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator()); + $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); $value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', trim($operand)); - $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator()); + $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/'); $value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? ''); $match = []; @@ -116,17 +116,22 @@ public static function convertToNumberIfPercent(string &$operand): bool public static function convertToNumberIfCurrency(string &$operand): bool { $currencyRegexp = self::currencyMatcherRegexp(); - $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator()); + $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); $value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $operand); $match = []; if ($value !== null && preg_match($currencyRegexp, $value, $match, PREG_UNMATCHED_AS_NULL)) { //Determine the sign $sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? ''; + $decimalSeparator = StringHelper::getDecimalSeparator(); //Cast to a float - $operand = (float) ($sign . ($match['PostfixedValue'] ?? $match['PrefixedValue'])); + $intermediate = (string) ($match['PostfixedValue'] ?? $match['PrefixedValue']); + $intermediate = str_replace($decimalSeparator, '.', $intermediate); + if (is_numeric($intermediate)) { + $operand = (float) ($sign . str_replace($decimalSeparator, '.', $intermediate)); - return true; + return true; + } } return false; @@ -134,8 +139,8 @@ public static function convertToNumberIfCurrency(string &$operand): bool public static function currencyMatcherRegexp(): string { - $currencyCodes = sprintf(self::CURRENCY_CONVERSION_LIST, preg_quote(StringHelper::getCurrencyCode())); - $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator()); + $currencyCodes = sprintf(self::CURRENCY_CONVERSION_LIST, preg_quote(StringHelper::getCurrencyCode(), '/')); + $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/'); return '~^(?:(?: *(?[-+])? *(?[' . $currencyCodes . ']) *(?[-+])? *(?[0-9]+[' . $decimalSeparator . ']?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?[-+])? *(?[0-9]+' . $decimalSeparator . '?[0-9]*(?:E[-+]?[0-9]*)?) *(?[' . $currencyCodes . ']) *))$~ui'; } diff --git a/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php b/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php index 266f1b2bb1..d7c2ffdc25 100644 --- a/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php +++ b/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php @@ -28,7 +28,7 @@ final class StructuredReference implements Operand self::ITEM_SPECIFIER_TOTALS, ]; - private const TABLE_REFERENCE = '/([\p{L}_\\\\][\p{L}\p{N}\._]+)?(\[(?:[^\]\[]+|(?R))*+\])/miu'; + private const TABLE_REFERENCE = '/([\p{L}_\\\][\p{L}\p{N}\._]+)?(\[(?:[^\]\[]+|(?R))*+\])/miu'; private string $value; @@ -190,8 +190,8 @@ private function adjustRowReference(string $columnName, string $reference, Cell { if ($columnName !== '') { $cellReference = $columnId . $cell->getRow(); - $pattern1 = '/\[' . preg_quote($columnName) . '\]/miu'; - $pattern2 = '/@' . preg_quote($columnName) . '/miu'; + $pattern1 = '/\[' . preg_quote($columnName, '/') . '\]/miu'; + $pattern2 = '/@' . preg_quote($columnName, '/') . '/miu'; if (preg_match($pattern1, $reference) === 1) { $reference = preg_replace($pattern1, $cellReference, $reference); } elseif (preg_match($pattern2, $reference) === 1) { @@ -328,7 +328,7 @@ private function getColumnsForColumnReference(string $reference, int $startRow, $cellFrom = "{$columnId}{$startRow}"; $cellTo = "{$columnId}{$endRow}"; $cellReference = ($cellFrom === $cellTo) ? $cellFrom : "{$cellFrom}:{$cellTo}"; - $pattern = '/\[' . preg_quote($columnName) . '\]/mui'; + $pattern = '/\[' . preg_quote($columnName, '/') . '\]/mui'; if (preg_match($pattern, $reference) === 1) { $columnsSelected = true; $reference = preg_replace($pattern, $cellReference, $reference); diff --git a/src/PhpSpreadsheet/Calculation/Financial/Amortization.php b/src/PhpSpreadsheet/Calculation/Financial/Amortization.php index 72a71b295e..a531e31be6 100644 --- a/src/PhpSpreadsheet/Calculation/Financial/Amortization.php +++ b/src/PhpSpreadsheet/Calculation/Financial/Amortization.php @@ -80,6 +80,7 @@ public static function AMORDEGRC( $amortiseCoeff = self::getAmortizationCoefficient($rate); $rate *= $amortiseCoeff; + $rate = (float) (string) $rate; // ugly way to avoid rounding problem $fNRate = round($yearFrac * $rate * $cost, 0); $cost -= $fNRate; $fRest = $cost - $salvage; diff --git a/src/PhpSpreadsheet/Calculation/FormulaParser.php b/src/PhpSpreadsheet/Calculation/FormulaParser.php index 14c5ae50a6..e3bae6b9ac 100644 --- a/src/PhpSpreadsheet/Calculation/FormulaParser.php +++ b/src/PhpSpreadsheet/Calculation/FormulaParser.php @@ -219,7 +219,7 @@ private function parseToTokens(): void // scientific notation check if (strpos(self::OPERATORS_SN, $this->formula[$index]) !== false) { if (strlen($value) > 1) { - if (preg_match('/^[1-9]{1}(\\.\\d+)?E{1}$/', $this->formula[$index]) != 0) { + if (preg_match('/^[1-9]{1}(\.\d+)?E{1}$/', $this->formula[$index]) != 0) { $value .= $this->formula[$index]; ++$index; diff --git a/src/PhpSpreadsheet/Calculation/Functions.php b/src/PhpSpreadsheet/Calculation/Functions.php index 0ec396f6e3..1534aea945 100644 --- a/src/PhpSpreadsheet/Calculation/Functions.php +++ b/src/PhpSpreadsheet/Calculation/Functions.php @@ -663,7 +663,7 @@ public static function expandDefinedName(string $coordinate, Cell $cell): string public static function trimTrailingRange(string $coordinate): string { - return (string) preg_replace('/:[\\w\$]+$/', '', $coordinate); + return (string) preg_replace('/:[\w\$]+$/', '', $coordinate); } public static function trimSheetFromCellReference(string $coordinate): string diff --git a/src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php b/src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php index 371ad8b3d1..8282ea289c 100644 --- a/src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php +++ b/src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php @@ -6,10 +6,10 @@ class WildcardMatch { private const SEARCH_SET = [ '~~', // convert double tilde to unprintable value - '~\\*', // convert tilde backslash asterisk to [*] (matches literal asterisk in regexp) - '\\*', // convert backslash asterisk to .* (matches string of any length in regexp) - '~\\?', // convert tilde backslash question to [?] (matches literal question mark in regexp) - '\\?', // convert backslash question to . (matches one character in regexp) + '~\*', // convert tilde backslash asterisk to [*] (matches literal asterisk in regexp) + '\*', // convert backslash asterisk to .* (matches string of any length in regexp) + '~\?', // convert tilde backslash question to [?] (matches literal question mark in regexp) + '\?', // convert backslash question to . (matches one character in regexp) "\x1c", // convert original double tilde to single tilde ]; diff --git a/src/PhpSpreadsheet/Calculation/Kasko.php b/src/PhpSpreadsheet/Calculation/Kasko.php new file mode 100644 index 0000000000..0958f5e32b --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Kasko.php @@ -0,0 +1,57 @@ += 80400) { + return round( + (float) (string) $number, + $digits, + RoundingMode::AwayFromZero //* @phpstan-ignore-line + ); + } + if ($number < 0.0) { return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); } @@ -79,8 +89,8 @@ public static function up($number, $digits) * * Rounds a number down to a specified number of decimal places * - * @param array|float $number Number to round, or can be an array of numbers - * @param array|int $digits Number of digits to which you want to round $number, or can be an array of numbers + * @param null|array|float|string $number Number to round, or can be an array of numbers + * @param array|float|int|string $digits Number of digits to which you want to round $number, or can be an array of numbers * * @return array|float|string Rounded Number, or a string containing an error * If an array of numbers is passed as the argument, then the returned result will also be an array @@ -103,6 +113,14 @@ public static function down($number, $digits) return 0.0; } + if (PHP_VERSION_ID >= 80400) { + return round( + (float) (string) $number, + $digits, + RoundingMode::TowardsZero //* @phpstan-ignore-line + ); + } + if ($number < 0.0) { return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); } @@ -118,7 +136,7 @@ public static function down($number, $digits) * @param mixed $number Expect float. Number to round, or can be an array of numbers * @param mixed $multiple Expect int. Multiple to which you want to round, or can be an array of numbers. * - * @return array|float|string Rounded Number, or a string containing an error + * @return array|float|int|string Rounded Number, or a string containing an error * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ @@ -187,7 +205,7 @@ public static function even($number) * * @param array|float $number Number to round, or can be an array of numbers * - * @return array|float|string Rounded Number, or a string containing an error + * @return array|float|int|string Rounded Number, or a string containing an error * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php b/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php index 1a797c8a24..56b0861c6e 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php @@ -66,8 +66,8 @@ public static function sumErroringStrings(...$args) $returnValue += (int) $arg; } elseif (ErrorValue::isError($arg)) { return $arg; - // ignore non-numerics from cell, but fail as literals (except null) } elseif ($arg !== null && !Functions::isCellValue($k)) { + // ignore non-numerics from cell, but fail as literals (except null) return ExcelError::VALUE(); } } diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php b/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php index 943e209dd2..bd128ba64d 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php @@ -3,7 +3,6 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig; use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; -use PhpOffice\PhpSpreadsheet\Calculation\Exception; class Trunc { @@ -13,11 +12,14 @@ class Trunc * TRUNC. * * Truncates value to the number of fractional digits by number_digits. + * This will probably not be the precise result in the unlikely + * event that the number of digits to the left of the decimal + * plus the number of digits to the right exceeds PHP_FLOAT_DIG + * (or possibly that value minus 1). + * Excel is unlikely to do any better. * - * @param array|float $value - * Or can be an array of values - * @param array|int $digits - * Or can be an array of values + * @param null|array|float|string $value Or can be an array of values + * @param array|float|int|string $digits Or can be an array of values * * @return array|float|string Truncated value, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array @@ -29,22 +31,6 @@ public static function evaluate($value = 0, $digits = 0) return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $digits); } - try { - $value = Helpers::validateNumericNullBool($value); - $digits = Helpers::validateNumericNullSubstitution($digits, null); - } catch (Exception $e) { - return $e->getMessage(); - } - - $digits = floor($digits); - - // Truncate - $adjust = 10 ** $digits; - - if (($digits > 0) && (rtrim((string) (int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) { - return $value; - } - - return ((int) ($value * $adjust)) / $adjust; + return Round::down($value, $digits); } } diff --git a/src/PhpSpreadsheet/Calculation/TextData/Extract.php b/src/PhpSpreadsheet/Calculation/TextData/Extract.php index 519607c080..24ddff2ec5 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Extract.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Extract.php @@ -261,7 +261,7 @@ private static function buildDelimiter($delimiter): string $delimiter = Functions::flattenArray($delimiter); $quotedDelimiters = array_map( function ($delimiter) { - return preg_quote($delimiter ?? ''); + return preg_quote($delimiter ?? '', '/'); }, $delimiter ); @@ -270,7 +270,7 @@ function ($delimiter) { return '(' . $delimiters . ')'; } - return '(' . preg_quote($delimiter ?? '') . ')'; + return '(' . preg_quote($delimiter ?? '', '/') . ')'; } private static function matchFlags(int $matchMode): string diff --git a/src/PhpSpreadsheet/Calculation/TextData/Format.php b/src/PhpSpreadsheet/Calculation/TextData/Format.php index 5e0762fd69..5352b6d02b 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Format.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Format.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; +use Composer\Pcre\Preg; use DateTimeInterface; use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; @@ -128,8 +129,11 @@ public static function TEXTFORMAT($value, $format) $value = Helpers::extractString($value); $format = Helpers::extractString($format); - if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) { - $value = DateTimeExcel\DateValue::fromString($value) + DateTimeExcel\TimeValue::fromString($value); + if (!is_numeric($value) && Date::isDateTimeFormatCode($format) && !Preg::isMatch('/^\s*\d+(\s+\d+)+\s*$/', $value)) { + $value1 = DateTimeExcel\DateValue::fromString($value); + $value2 = DateTimeExcel\TimeValue::fromString($value); + /** @var float|int|string */ + $value = (is_numeric($value1) && is_numeric($value2)) ? ($value1 + $value2) : (is_numeric($value1) ? $value1 : (is_numeric($value2) ? $value2 : $value)); } return (string) NumberFormat::toFormattedString($value, $format); @@ -140,7 +144,7 @@ public static function TEXTFORMAT($value, $format) * * @return mixed */ - private static function convertValue($value) + private static function convertValue($value, bool $spacesMeanZero = false) { $value = $value ?? 0; if (is_bool($value)) { @@ -150,6 +154,12 @@ private static function convertValue($value) throw new CalcExp(ExcelError::VALUE()); } } + if (is_string($value)) { + $value = trim($value); + if ($spacesMeanZero && $value === '') { + $value = 0; + } + } return $value; } @@ -181,6 +191,9 @@ public static function VALUE($value = '') '', trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode()) ); + if ($numberValue === '') { + return ExcelError::VALUE(); + } if (is_numeric($numberValue)) { return (float) $numberValue; } @@ -277,7 +290,7 @@ public static function NUMBERVALUE($value = '', $decimalSeparator = null, $group } try { - $value = self::convertValue($value); + $value = self::convertValue($value, true); $decimalSeparator = self::getDecimalSeparator($decimalSeparator); $groupSeparator = self::getGroupSeparator($groupSeparator); } catch (CalcExp $e) { @@ -285,12 +298,12 @@ public static function NUMBERVALUE($value = '', $decimalSeparator = null, $group } if (!is_numeric($value)) { - $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE); + $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator, '/') . '/', $value, $matches, PREG_OFFSET_CAPTURE); if ($decimalPositions > 1) { return ExcelError::VALUE(); } - $decimalOffset = array_pop($matches[0])[1]; // @phpstan-ignore-line - if (strpos($value, $groupSeparator, $decimalOffset) !== false) { + $decimalOffset = array_pop($matches[0])[1] ?? null; + if ($decimalOffset === null || strpos($value, $groupSeparator, $decimalOffset) !== false) { return ExcelError::VALUE(); } diff --git a/src/PhpSpreadsheet/Calculation/TextData/Text.php b/src/PhpSpreadsheet/Calculation/TextData/Text.php index 8e6a575a29..b8a730767c 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Text.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Text.php @@ -193,7 +193,7 @@ private static function buildDelimiter($delimiter): string if (is_array($delimiter) && count($valueSet) > 1) { $quotedDelimiters = array_map( function ($delimiter) { - return preg_quote($delimiter ?? ''); + return preg_quote($delimiter ?? '', '/'); }, $valueSet ); @@ -202,7 +202,7 @@ function ($delimiter) { return '(' . $delimiters . ')'; } - return '(' . preg_quote(/** @scrutinizer ignore-type */ Functions::flattenSingleValue($delimiter)) . ')'; + return '(' . preg_quote(/** @scrutinizer ignore-type */ Functions::flattenSingleValue($delimiter), '/') . ')'; } private static function matchFlags(bool $matchMode): string diff --git a/src/PhpSpreadsheet/Calculation/TextData/Trim.php b/src/PhpSpreadsheet/Calculation/TextData/Trim.php index 27eceb9388..c4808cc742 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Trim.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Trim.php @@ -26,7 +26,7 @@ public static function nonPrintable($stringValue = '') $stringValue = Helpers::extractString($stringValue); - return (string) preg_replace('/[\\x00-\\x1f]/', '', "$stringValue"); + return (string) preg_replace('/[\x00-\x1f]/', '', "$stringValue"); } /** diff --git a/src/PhpSpreadsheet/Calculation/Web.php b/src/PhpSpreadsheet/Calculation/Web.php index f4dd40e57d..99809d35bf 100644 --- a/src/PhpSpreadsheet/Calculation/Web.php +++ b/src/PhpSpreadsheet/Calculation/Web.php @@ -21,7 +21,7 @@ class Web * Use the webService() method in the Web\Service class instead * @see Web\Service::webService() * - * @return string the output resulting from a call to the webservice + * @return ?string the output resulting from a call to the webservice */ public static function WEBSERVICE(string $url) { diff --git a/src/PhpSpreadsheet/Calculation/Web/Service.php b/src/PhpSpreadsheet/Calculation/Web/Service.php index 697d3a6143..2b9b75df49 100644 --- a/src/PhpSpreadsheet/Calculation/Web/Service.php +++ b/src/PhpSpreadsheet/Calculation/Web/Service.php @@ -2,9 +2,9 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Web; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; -use PhpOffice\PhpSpreadsheet\Settings; -use Psr\Http\Client\ClientExceptionInterface; +use PhpOffice\PhpSpreadsheet\Cell\Cell; class Service { @@ -16,36 +16,51 @@ class Service * Excel Function: * Webservice(url) * - * @return string the output resulting from a call to the webservice + * @param mixed $url + * + * @return ?string the output resulting from a call to the webservice */ - public static function webService(string $url) + public static function webService($url, ?Cell $cell = null) { + if (is_array($url)) { + $url = Functions::flattenSingleValue($url); + } + if (!is_string($url)) { + return ExcelError::VALUE(); // Invalid URL length + } $url = trim($url); - if (strlen($url) > 2048) { + if (mb_strlen($url) > 2048) { return ExcelError::VALUE(); // Invalid URL length } - - if (!preg_match('/^http[s]?:\/\//', $url)) { + $parsed = parse_url($url); + $scheme = $parsed['scheme'] ?? ''; + if ($scheme !== 'http' && $scheme !== 'https') { return ExcelError::VALUE(); // Invalid protocol } - - // Get results from the the webservice - $client = Settings::getHttpClient(); - $requestFactory = Settings::getRequestFactory(); - $request = $requestFactory->createRequest('GET', $url); - - try { - $response = $client->sendRequest($request); - } catch (ClientExceptionInterface $e) { - return ExcelError::VALUE(); // cURL error + $domainWhiteList = []; + if ($cell !== null) { + $parent = $cell->getWorksheet()->getParent(); + if ($parent !== null) { + $domainWhiteList = $parent->getDomainWhiteList(); + } } - - if ($response->getStatusCode() != 200) { - return ExcelError::VALUE(); // cURL error + $host = $parsed['host'] ?? ''; + if (!in_array($host, $domainWhiteList, true)) { + return ($cell === null) ? null : '#Not Yet Implemented'; // will be converted to oldCalculatedValue or null } - $output = $response->getBody()->getContents(); - if (strlen($output) > 32767) { + // Get results from the webservice + $ctxArray = [ + 'http' => [ + 'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + ], + ]; + if ($scheme === 'https') { + $ctxArray['ssl'] = ['crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT]; + } + $ctx = stream_context_create($ctxArray); + $output = @file_get_contents($url, false, $ctx); + if ($output === false || mb_strlen($output) > 32767) { return ExcelError::VALUE(); // Output not a string or too long } diff --git a/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php b/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php index 1bf73ba829..c0fb387753 100644 --- a/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php +++ b/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php @@ -51,8 +51,9 @@ public function bindValue(Cell $cell, $value = null) return $this->setImproperFraction($matches, $cell); } - $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator()); - $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator()); + $decimalSeparatorNoPreg = StringHelper::getDecimalSeparator(); + $decimalSeparator = preg_quote($decimalSeparatorNoPreg, '/'); + $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); // Check for percentage if (preg_match('/^\-?\d*' . $decimalSeparator . '?\d*\s?\%$/', preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value))) { @@ -64,7 +65,7 @@ public function bindValue(Cell $cell, $value = null) // Convert value to number $sign = ($matches['PrefixedSign'] ?? $matches['PrefixedSign2'] ?? $matches['PostfixedSign']) ?? null; $currencyCode = $matches['PrefixedCurrency'] ?? $matches['PostfixedCurrency']; - $value = (float) ($sign . trim(str_replace([$decimalSeparator, $currencyCode, ' ', '-'], ['.', '', '', ''], preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value)))); // @phpstan-ignore-line + $value = (float) ($sign . trim(str_replace([$decimalSeparatorNoPreg, $currencyCode, ' ', '-'], ['.', '', '', ''], preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value)))); // @phpstan-ignore-line return $this->setCurrency($value, $cell, $currencyCode); // @phpstan-ignore-line } diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index e9e41d7c7b..bbfbd26af5 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -67,7 +67,7 @@ class Cell /** * Attributes of the formula. * - * @var mixed + * @var ?array */ private $formulaAttributes; @@ -767,26 +767,14 @@ public function setXfIndex(int $indexValue): self return $this->updateInCollection(); } - /** - * Set the formula attributes. - * - * @param mixed $attributes - * - * @return $this - */ - public function setFormulaAttributes($attributes): self + public function setFormulaAttributes(?array $attributes): self { $this->formulaAttributes = $attributes; return $this; } - /** - * Get the formula attributes. - * - * @return mixed - */ - public function getFormulaAttributes() + public function getFormulaAttributes(): ?array { return $this->formulaAttributes; } diff --git a/src/PhpSpreadsheet/Cell/DataValidator.php b/src/PhpSpreadsheet/Cell/DataValidator.php index 0e395a7ff9..692f316ec0 100644 --- a/src/PhpSpreadsheet/Cell/DataValidator.php +++ b/src/PhpSpreadsheet/Cell/DataValidator.php @@ -20,7 +20,7 @@ class DataValidator */ public function isValid(Cell $cell) { - if (!$cell->hasDataValidation()) { + if (!$cell->hasDataValidation() || $cell->getDataValidation()->getType() === DataValidation::TYPE_NONE) { return true; } @@ -31,13 +31,55 @@ public function isValid(Cell $cell) return false; } - // TODO: write check on all cases - switch ($dataValidation->getType()) { - case DataValidation::TYPE_LIST: - return $this->isValueInList($cell); + $returnValue = false; + $type = $dataValidation->getType(); + if ($type === DataValidation::TYPE_LIST) { + $returnValue = $this->isValueInList($cell); + } elseif ($type === DataValidation::TYPE_WHOLE) { + if (!is_numeric($cellValue) || fmod((float) $cellValue, 1) != 0) { + $returnValue = false; + } else { + $returnValue = $this->numericOperator($dataValidation, (int) $cellValue); + } + } elseif ($type === DataValidation::TYPE_DECIMAL || $type === DataValidation::TYPE_DATE || $type === DataValidation::TYPE_TIME) { + if (!is_numeric($cellValue)) { + $returnValue = false; + } else { + $returnValue = $this->numericOperator($dataValidation, (float) $cellValue); + } + } elseif ($type === DataValidation::TYPE_TEXTLENGTH) { + $returnValue = $this->numericOperator($dataValidation, mb_strlen((string) $cellValue)); + } + + return $returnValue; + } + + /** @param float|int $cellValue */ + private function numericOperator(DataValidation $dataValidation, $cellValue): bool + { + $operator = $dataValidation->getOperator(); + $formula1 = $dataValidation->getFormula1(); + $formula2 = $dataValidation->getFormula2(); + $returnValue = false; + if ($operator === DataValidation::OPERATOR_BETWEEN) { + $returnValue = $cellValue >= $formula1 && $cellValue <= $formula2; + } elseif ($operator === DataValidation::OPERATOR_NOTBETWEEN) { + $returnValue = $cellValue < $formula1 || $cellValue > $formula2; + } elseif ($operator === DataValidation::OPERATOR_EQUAL) { + $returnValue = $cellValue == $formula1; + } elseif ($operator === DataValidation::OPERATOR_NOTEQUAL) { + $returnValue = $cellValue != $formula1; + } elseif ($operator === DataValidation::OPERATOR_LESSTHAN) { + $returnValue = $cellValue < $formula1; + } elseif ($operator === DataValidation::OPERATOR_LESSTHANOREQUAL) { + $returnValue = $cellValue <= $formula1; + } elseif ($operator === DataValidation::OPERATOR_GREATERTHAN) { + $returnValue = $cellValue > $formula1; + } elseif ($operator === DataValidation::OPERATOR_GREATERTHANOREQUAL) { + $returnValue = $cellValue >= $formula1; } - return false; + return $returnValue; } /** diff --git a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php index 92e1d25eac..33d7133671 100644 --- a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php +++ b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php @@ -60,7 +60,7 @@ public static function dataTypeForValue($value) return DataType::TYPE_INLINE; } elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=') { return DataType::TYPE_FORMULA; - } elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) { + } elseif (preg_match('/^[\+\-]?(\d+\.?\d*|\d*\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) { $tValue = ltrim($value, '+-'); if (is_string($value) && strlen($tValue) > 1 && $tValue[0] === '0' && $tValue[1] !== '.') { return DataType::TYPE_STRING; diff --git a/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php b/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php index e1f0f90add..b5e70d3a19 100644 --- a/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php +++ b/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php @@ -3,12 +3,12 @@ namespace PhpOffice\PhpSpreadsheet\Chart\Renderer; /** - * Jpgraph is not oficially maintained in Composer. + * Jpgraph is not officially maintained by Composer at packagist.org. * * This renderer implementation uses package * https://packagist.org/packages/mitoteam/jpgraph * - * This package is up to date for August 2022 and has PHP 8.1 support. + * This package is up to date for June 2023 and has PHP 8.2 support. */ class MtJpGraphRenderer extends JpGraphRendererBase { @@ -29,7 +29,7 @@ protected static function init(): void 'regstat', 'scatter', 'stock', - ]); + ], true); // enable Extended mode $loaded = true; } diff --git a/src/PhpSpreadsheet/Document/Properties.php b/src/PhpSpreadsheet/Document/Properties.php index 162e818036..9dd44ce31c 100644 --- a/src/PhpSpreadsheet/Document/Properties.php +++ b/src/PhpSpreadsheet/Document/Properties.php @@ -107,6 +107,8 @@ class Properties */ private $customProperties = []; + private string $hyperlinkBase = ''; + /** * Create a new Document Properties instance. */ @@ -172,8 +174,8 @@ private static function intOrFloatTimestamp($timestamp) $timestamp = (float) $timestamp; } else { $timestamp = (string) preg_replace('/[.][0-9]*$/', '', $timestamp); - $timestamp = (string) preg_replace('/^(\\d{4})- (\\d)/', '$1-0$2', $timestamp); - $timestamp = (string) preg_replace('/^(\\d{4}-\\d{2})- (\\d)/', '$1-0$2', $timestamp); + $timestamp = (string) preg_replace('/^(\d{4})- (\d)/', '$1-0$2', $timestamp); + $timestamp = (string) preg_replace('/^(\d{4}-\d{2})- (\d)/', '$1-0$2', $timestamp); $timestamp = (float) (new DateTime($timestamp))->format('U'); } } @@ -534,4 +536,16 @@ public static function convertPropertyType(string $propertyType): string { return self::PROPERTY_TYPE_ARRAY[$propertyType] ?? self::PROPERTY_TYPE_UNKNOWN; } + + public function getHyperlinkBase(): string + { + return $this->hyperlinkBase; + } + + public function setHyperlinkBase(string $hyperlinkBase): self + { + $this->hyperlinkBase = $hyperlinkBase; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Helper/Downloader.php b/src/PhpSpreadsheet/Helper/Downloader.php index e66ae42584..e5911432ba 100644 --- a/src/PhpSpreadsheet/Helper/Downloader.php +++ b/src/PhpSpreadsheet/Helper/Downloader.php @@ -23,19 +23,17 @@ class Downloader public function __construct(string $folder, string $filename, ?string $filetype = null) { - if ((is_dir($folder) === false) || (is_readable($folder) === false)) { - throw new Exception("Folder {$folder} is not accessable"); - } - $filepath = "{$folder}/{$filename}"; - $this->filepath = (string) realpath($filepath); - $this->filename = basename($filepath); - if ((file_exists($this->filepath) === false) || (is_readable($this->filepath) === false)) { - throw new Exception("{$this->filename} not found, or cannot be read"); + clearstatcache(); + $filepath = realpath("{$folder}/{$filename}"); + if ($filepath === false || !is_file($filepath) || !is_readable($filepath)) { + throw new Exception('File not found, or cannot be read'); } + $this->filepath = $filepath; + $this->filename = basename($this->filepath); - $filetype ??= pathinfo($filename, PATHINFO_EXTENSION); + $filetype ??= pathinfo($this->filename, PATHINFO_EXTENSION); if (array_key_exists(strtolower($filetype), self::CONTENT_TYPES) === false) { - throw new Exception("Invalid filetype: {$filetype} cannot be downloaded"); + throw new Exception('Invalid filetype: cannot be downloaded'); } $this->filetype = strtolower($filetype); } diff --git a/src/PhpSpreadsheet/IOFactory.php b/src/PhpSpreadsheet/IOFactory.php index c088397d81..901d901c07 100644 --- a/src/PhpSpreadsheet/IOFactory.php +++ b/src/PhpSpreadsheet/IOFactory.php @@ -30,7 +30,7 @@ abstract class IOFactory public const WRITER_CSV = 'Csv'; public const WRITER_HTML = 'Html'; - /** @var string[] */ + /** @var array> */ private static $readers = [ self::READER_XLSX => Reader\Xlsx::class, self::READER_XLS => Reader\Xls::class, @@ -236,4 +236,16 @@ public static function registerReader(string $readerType, string $readerClass): self::$readers[$readerType] = $readerClass; } + + /** + * @return array> + * + * @internal + * + * @codeCoverageIgnore + */ + public static function getReaders(): array + { + return self::$readers; + } } diff --git a/src/PhpSpreadsheet/Reader/BaseReader.php b/src/PhpSpreadsheet/Reader/BaseReader.php index aa380aa953..e51f47e29b 100644 --- a/src/PhpSpreadsheet/Reader/BaseReader.php +++ b/src/PhpSpreadsheet/Reader/BaseReader.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader; +use Closure; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; @@ -44,6 +45,15 @@ abstract class BaseReader implements IReader */ protected $loadSheetsOnly; + /** + * Allow external images. Use with caution. + * Improper specification of these within a spreadsheet + * can subject the caller to security exploits. + * + * @var bool + */ + protected $allowExternalImages = false; + /** * IReadFilter instance. * @@ -59,6 +69,9 @@ abstract class BaseReader implements IReader */ protected $securityScanner; + /** @var null|Closure(string):bool function to return whether image path is okay */ + protected ?Closure $isWhitelisted = null; + public function __construct() { $this->readFilter = new DefaultReadFilter(); @@ -160,6 +173,12 @@ protected function processFlags(int $flags): void if (((bool) ($flags & self::SKIP_EMPTY_CELLS) || (bool) ($flags & self::IGNORE_EMPTY_CELLS)) === true) { $this->setReadEmptyCells(false); } + if (((bool) ($flags & self::ALLOW_EXTERNAL_IMAGES)) === true) { + $this->setAllowExternalImages(true); + } + if (((bool) ($flags & self::DONT_ALLOW_EXTERNAL_IMAGES)) === true) { + $this->setAllowExternalImages(false); + } } protected function loadSpreadsheetFromFile(string $filename): Spreadsheet @@ -203,4 +222,38 @@ protected function openFile(string $filename): void $this->fileHandle = $fileHandle; } + + /** + * USE WITH CAUTION (and in conjunction with setIsWhiteListed)! + * Allow external images; + * these can be specified within a spreadsheet + * in a way that can subject the caller to security exploits. + */ + public function setAllowExternalImages(bool $allowExternalImages) + { + $this->allowExternalImages = $allowExternalImages; + + return $this; + } + + public function getAllowExternalImages() + { + return $this->allowExternalImages; + } + + /** + * USE WITH CAUTION! + * Supply a callback to determine whether a path should be whitelisted, + * used in conjunction with setAllowExternalImages; + * supplying a method which might return true + * can subject the caller to security exploits. + * + * @param Closure(string):bool $isWhitelisted + */ + public function setIsWhitelisted(Closure $isWhitelisted): self + { + $this->isWhitelisted = $isWhitelisted; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Reader/Csv.php b/src/PhpSpreadsheet/Reader/Csv.php index be9a2a32c1..b866892757 100644 --- a/src/PhpSpreadsheet/Reader/Csv.php +++ b/src/PhpSpreadsheet/Reader/Csv.php @@ -10,6 +10,7 @@ use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; +use Throwable; class Csv extends BaseReader { @@ -75,9 +76,17 @@ class Csv extends BaseReader /** * The character that can escape the enclosure. * - * @var string + * @var ?string + */ + private $escapeCharacter; + + /** + * The character that will be supplied to fgetcsv + * when escapeCharacter is null. + * It is anticipated that it will conditionally be set + * to null-string for Php9 and above. */ - private $escapeCharacter = '\\'; + private static string $defaultEscapeCharacter = PHP_VERSION_ID < 90000 ? '\\' : ''; /** * Callback for setting defaults in construction. @@ -198,7 +207,7 @@ protected function inferSeparator(): void return; } - $inferenceEngine = new Delimiter($this->fileHandle, $this->escapeCharacter, $this->enclosure); + $inferenceEngine = new Delimiter($this->fileHandle, $this->escapeCharacter ?? self::$defaultEscapeCharacter, $this->enclosure); // If number of lines is 0, nothing to infer : fall back to the default if ($inferenceEngine->linesCounted() === 0) { @@ -240,11 +249,11 @@ public function listWorksheetInfo(string $filename): array $worksheetInfo[0]['totalColumns'] = 0; // Loop through each line of the file in turn - $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); + $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); while (is_array($rowData)) { ++$worksheetInfo[0]['totalRows']; $worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1); - $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); + $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); } $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1); @@ -287,6 +296,12 @@ private function openFileOrMemory(string $filename): void if (!$fhandle) { throw new Exception($filename . ' is an Invalid Spreadsheet file.'); } + if ($this->inputEncoding === 'UTF-8') { + $encoding = self::guessEncodingBom($filename); + if ($encoding !== '') { + $this->inputEncoding = $encoding; + } + } if ($this->inputEncoding === self::GUESS_ENCODING) { $this->inputEncoding = self::guessEncoding($filename, $this->fallbackEncoding); } @@ -314,7 +329,7 @@ public function setTestAutoDetect(bool $value): self private function setAutoDetect(?string $value): ?string { $retVal = null; - if ($value !== null && $this->testAutodetect) { + if ($value !== null && $this->testAutodetect && PHP_VERSION_ID < 90000) { $retVal2 = @ini_set('auto_detect_line_endings', $value); if (is_string($retVal2)) { $retVal = $retVal2; @@ -363,6 +378,20 @@ private function loadStringOrFile(string $filename, Spreadsheet $spreadsheet, bo // Deprecated in Php8.1 $iniset = $this->setAutoDetect('1'); + try { + $this->loadStringOrFile2($filename, $spreadsheet, $dataUri); + $this->setAutoDetect($iniset); + } catch (Throwable $e) { + $this->setAutoDetect($iniset); + + throw $e; + } + + return $spreadsheet; + } + + private function loadStringOrFile2(string $filename, Spreadsheet $spreadsheet, bool $dataUri): void + { // Open file if ($dataUri) { $this->openDataUri($filename); @@ -387,7 +416,7 @@ private function loadStringOrFile(string $filename, Spreadsheet $spreadsheet, bo $outRow = 0; // Loop through each line of the file in turn - $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); + $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); $valueBinder = Cell::getValueBinder(); $preserveBooleanString = method_exists($valueBinder, 'getBooleanConversion') && $valueBinder->getBooleanConversion(); while (is_array($rowData)) { @@ -414,17 +443,12 @@ private function loadStringOrFile(string $filename, Spreadsheet $spreadsheet, bo } ++$columnLetter; } - $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); + $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); ++$currentRow; } // Close file fclose($fileHandle); - - $this->setAutoDetect($iniset); - - // Return - return $spreadsheet; } /** @@ -529,8 +553,17 @@ public function getContiguous(): bool return $this->contiguous; } + /** + * Php9 intends to drop support for this parameter in fgetcsv. + * Not yet ready to mark deprecated in order to give users + * a migration path. + */ public function setEscapeCharacter(string $escapeCharacter): self { + if (PHP_VERSION_ID >= 90000 && $escapeCharacter !== '') { + throw new ReaderException('Escape character must be null string for Php9+'); + } + $this->escapeCharacter = $escapeCharacter; return $this; @@ -538,7 +571,7 @@ public function setEscapeCharacter(string $escapeCharacter): self public function getEscapeCharacter(): string { - return $this->escapeCharacter; + return $this->escapeCharacter ?? self::$defaultEscapeCharacter; } /** @@ -607,17 +640,15 @@ private static function guessEncodingTestBom(string &$encoding, string $first4, } } - private static function guessEncodingBom(string $filename): string + public static function guessEncodingBom(string $filename, ?string $convertString = null): string { $encoding = ''; - $first4 = file_get_contents($filename, false, null, 0, 4); - if ($first4 !== false) { - self::guessEncodingTestBom($encoding, $first4, self::UTF8_BOM, 'UTF-8'); - self::guessEncodingTestBom($encoding, $first4, self::UTF16BE_BOM, 'UTF-16BE'); - self::guessEncodingTestBom($encoding, $first4, self::UTF32BE_BOM, 'UTF-32BE'); - self::guessEncodingTestBom($encoding, $first4, self::UTF32LE_BOM, 'UTF-32LE'); - self::guessEncodingTestBom($encoding, $first4, self::UTF16LE_BOM, 'UTF-16LE'); - } + $first4 = $convertString ?? (string) file_get_contents($filename, false, null, 0, 4); + self::guessEncodingTestBom($encoding, $first4, self::UTF8_BOM, 'UTF-8'); + self::guessEncodingTestBom($encoding, $first4, self::UTF16BE_BOM, 'UTF-16BE'); + self::guessEncodingTestBom($encoding, $first4, self::UTF32BE_BOM, 'UTF-32BE'); + self::guessEncodingTestBom($encoding, $first4, self::UTF32LE_BOM, 'UTF-32LE'); + self::guessEncodingTestBom($encoding, $first4, self::UTF16LE_BOM, 'UTF-16LE'); return $encoding; } @@ -643,4 +674,62 @@ public function getPreserveNullString(): bool { return $this->preserveNullString; } + + /** + * Php8.4 deprecates use of anything other than null string + * as escape Character. + * + * @param resource $stream + * + * @return array|false + */ + private static function getCsv( + $stream, + ?int $length = null, + string $separator = ',', + string $enclosure = '"', + ?string $escape = null + ) { + $escape = $escape ?? self::$defaultEscapeCharacter; + if (PHP_VERSION_ID >= 80400 && $escape !== '') { + return @fgetcsv($stream, $length, $separator, $enclosure, $escape); + } + + return fgetcsv($stream, $length, $separator, $enclosure, $escape); + } + + public static function affectedByPhp9( + string $filename, + string $inputEncoding = 'UTF-8', + ?string $delimiter = null, + string $enclosure = '"', + string $escapeCharacter = '\\' + ): bool { + if (PHP_VERSION_ID < 70400 || PHP_VERSION_ID >= 90000) { + throw new ReaderException('Function valid only for Php7.4 or Php8'); // @codeCoverageIgnore + } + $reader1 = new self(); + $reader1->setInputEncoding($inputEncoding) + ->setTestAutoDetect(true) + ->setEscapeCharacter($escapeCharacter) + ->setDelimiter($delimiter) + ->setEnclosure($enclosure); + $spreadsheet1 = $reader1->load($filename); + $sheet1 = $spreadsheet1->getActiveSheet(); + $array1 = $sheet1->toArray(null, false, false); + $spreadsheet1->disconnectWorksheets(); + + $reader2 = new self(); + $reader2->setInputEncoding($inputEncoding) + ->setTestAutoDetect(false) + ->setEscapeCharacter('') + ->setDelimiter($delimiter) + ->setEnclosure($enclosure); + $spreadsheet2 = $reader2->load($filename); + $sheet2 = $spreadsheet2->getActiveSheet(); + $array2 = $sheet2->toArray(null, false, false); + $spreadsheet2->disconnectWorksheets(); + + return $array1 !== $array2; + } } diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php index 1745532257..5bd572d6ef 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric.php @@ -11,7 +11,6 @@ use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; -use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; @@ -80,17 +79,15 @@ public function __construct() */ public function canRead(string $filename): bool { - // Check if gzlib functions are available - if (File::testFileNoThrow($filename) && function_exists('gzread')) { - // Read signature data (first 3 bytes) - $fh = fopen($filename, 'rb'); - if ($fh !== false) { - $data = fread($fh, 2); - fclose($fh); + $data = null; + if (File::testFileNoThrow($filename)) { + $data = $this->gzfileGetContents($filename); + if (strpos($data, self::NAMESPACE_GNM) === false) { + $data = ''; } } - return isset($data) && $data === chr(0x1F) . chr(0x8B); + return !empty($data); } private static function matchXml(XMLReader $xml, string $expectedLocalName): bool @@ -110,9 +107,13 @@ private static function matchXml(XMLReader $xml, string $expectedLocalName): boo public function listWorksheetNames($filename) { File::assertFile($filename); + if (!$this->canRead($filename)) { + throw new Exception($filename . ' is an invalid Gnumeric file.'); + } $xml = new XMLReader(); - $xml->xml($this->getSecurityScannerOrThrow()->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions()); + $contents = $this->gzfileGetContents($filename); + $xml->xml($contents); $xml->setParserProperty(2, true); $worksheetNames = []; @@ -139,9 +140,13 @@ public function listWorksheetNames($filename) public function listWorksheetInfo($filename) { File::assertFile($filename); + if (!$this->canRead($filename)) { + throw new Exception($filename . ' is an invalid Gnumeric file.'); + } $xml = new XMLReader(); - $xml->xml($this->getSecurityScannerOrThrow()->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions()); + $contents = $this->gzfileGetContents($filename); + $xml->xml($contents); $xml->setParserProperty(2, true); $worksheetInfo = []; @@ -185,13 +190,23 @@ public function listWorksheetInfo($filename) */ private function gzfileGetContents($filename) { - $file = @gzopen($filename, 'rb'); $data = ''; - if ($file !== false) { - while (!gzeof($file)) { - $data .= gzread($file, 1024); + $contents = @file_get_contents($filename); + if ($contents !== false) { + if (substr($contents, 0, 2) === "\x1f\x8b") { + // Check if gzlib functions are available + if (function_exists('gzdecode')) { + $contents = @gzdecode($contents); + if ($contents !== false) { + $data = $contents; + } + } + } else { + $data = $contents; } - gzclose($file); + } + if ($data !== '') { + $data = $this->getSecurityScannerOrThrow()->scan($data); } return $data; @@ -245,10 +260,13 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp { $this->spreadsheet = $spreadsheet; File::assertFile($filename); + if (!$this->canRead($filename)) { + throw new Exception($filename . ' is an invalid Gnumeric file.'); + } $gFileData = $this->gzfileGetContents($filename); - $xml2 = simplexml_load_string($this->getSecurityScannerOrThrow()->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); + $xml2 = simplexml_load_string($gFileData); $xml = self::testSimpleXml($xml2); $gnmXML = $xml->children(self::NAMESPACE_GNM); diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index 377a8add06..0505912ac9 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -8,6 +8,7 @@ use DOMText; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Document\Properties; use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -490,9 +491,12 @@ private function processDomElementImg(Worksheet $sheet, int &$row, string &$colu } } + private string $currentColumn = 'A'; + private function processDomElementTable(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'table') { + $this->currentColumn = 'A'; $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray); $column = $this->setTableStartColumn($column); if ($this->tableLevel > 1 && $row > 1) { @@ -512,7 +516,10 @@ private function processDomElementTable(Worksheet $sheet, int &$row, string &$co private function processDomElementTr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { - if ($child->nodeName === 'tr') { + if ($child->nodeName === 'col') { + $this->applyInlineStyle($sheet, -1, $this->currentColumn, $attributeArray); + ++$this->currentColumn; + } elseif ($child->nodeName === 'tr') { $column = $this->getTableStartColumn(); $cellContent = ''; $this->processDomElement($child, $sheet, $row, $column, $cellContent); @@ -685,10 +692,94 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet) if ($loaded === false) { throw new Exception('Failed to load ' . $filename . ' as a DOM Document', 0, $e ?? null); } + self::loadProperties($dom, $spreadsheet); return $this->loadDocument($dom, $spreadsheet); } + private static function loadProperties(DOMDocument $dom, Spreadsheet $spreadsheet): void + { + $properties = $spreadsheet->getProperties(); + foreach ($dom->getElementsByTagName('meta') as $meta) { + $metaContent = (string) $meta->getAttribute('content'); + if ($metaContent !== '') { + $metaName = (string) $meta->getAttribute('name'); + switch ($metaName) { + case 'author': + $properties->setCreator($metaContent); + + break; + case 'category': + $properties->setCategory($metaContent); + + break; + case 'company': + $properties->setCompany($metaContent); + + break; + case 'created': + $properties->setCreated($metaContent); + + break; + case 'description': + $properties->setDescription($metaContent); + + break; + case 'keywords': + $properties->setKeywords($metaContent); + + break; + case 'lastModifiedBy': + $properties->setLastModifiedBy($metaContent); + + break; + case 'manager': + $properties->setManager($metaContent); + + break; + case 'modified': + $properties->setModified($metaContent); + + break; + case 'subject': + $properties->setSubject($metaContent); + + break; + case 'title': + $properties->setTitle($metaContent); + + break; + default: + if (preg_match('/^custom[.](bool|date|float|int|string)[.](.+)$/', $metaName, $matches) === 1) { + switch ($matches[1]) { + case 'bool': + $properties->setCustomProperty($matches[2], (bool) $metaContent, Properties::PROPERTY_TYPE_BOOLEAN); + + break; + case 'float': + $properties->setCustomProperty($matches[2], (float) $metaContent, Properties::PROPERTY_TYPE_FLOAT); + + break; + case 'int': + $properties->setCustomProperty($matches[2], (int) $metaContent, Properties::PROPERTY_TYPE_INTEGER); + + break; + case 'date': + $properties->setCustomProperty($matches[2], $metaContent, Properties::PROPERTY_TYPE_DATE); + + break; + default: // string + $properties->setCustomProperty($matches[2], $metaContent, Properties::PROPERTY_TYPE_STRING); + } + } + } + } + } + if (!empty($dom->baseURI)) { + $properties->setHyperlinkBase($dom->baseURI); + } + } + private static function replaceNonAscii(array $matches): string { return '&#' . mb_ord($matches[0], 'UTF-8') . ';'; @@ -719,8 +810,10 @@ public function loadFromString($content, ?Spreadsheet $spreadsheet = null): Spre if ($loaded === false) { throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null); } + $spreadsheet = $spreadsheet ?? new Spreadsheet(); + self::loadProperties($dom, $spreadsheet); - return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet()); + return $this->loadDocument($dom, $spreadsheet); } /** @@ -790,7 +883,9 @@ private function applyInlineStyle(Worksheet &$sheet, $row, $column, $attributeAr return; } - if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { + if ($row <= 0 || $column === '') { + $cellStyle = new Style(); + } elseif (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { ++$columnTo; @@ -922,16 +1017,20 @@ private function applyInlineStyle(Worksheet &$sheet, $row, $column, $attributeAr break; case 'width': - $sheet->getColumnDimension($column)->setWidth( - (new CssDimension($styleValue ?? ''))->width() - ); + if ($column !== '') { + $sheet->getColumnDimension($column)->setWidth( + (new CssDimension($styleValue ?? ''))->width() + ); + } break; case 'height': - $sheet->getRowDimension($row)->setRowHeight( - (new CssDimension($styleValue ?? ''))->height() - ); + if ($row > 0) { + $sheet->getRowDimension($row)->setRowHeight( + (new CssDimension($styleValue ?? ''))->height() + ); + } break; @@ -985,7 +1084,10 @@ private function insertImage(Worksheet $sheet, $column, $row, array $attributes) $name = $attributes['alt'] ?? null; $drawing = new Drawing(); - $drawing->setPath($src); + $drawing->setPath($src, false, null, $this->allowExternalImages, $this->isWhitelisted); + if ($drawing->getPath() === '') { + return; + } $drawing->setWorksheet($sheet); $drawing->setCoordinates($column . $row); $drawing->setOffsetX(0); diff --git a/src/PhpSpreadsheet/Reader/IReader.php b/src/PhpSpreadsheet/Reader/IReader.php index 97afe3cb20..8b5115337e 100644 --- a/src/PhpSpreadsheet/Reader/IReader.php +++ b/src/PhpSpreadsheet/Reader/IReader.php @@ -11,6 +11,14 @@ interface IReader public const SKIP_EMPTY_CELLS = 4; public const IGNORE_EMPTY_CELLS = 4; + /** + * Allow external images. Use with caution. + * Improper specification of these within a spreadsheet + * can subject the caller to security exploits. + */ + public const ALLOW_EXTERNAL_IMAGES = 16; + public const DONT_ALLOW_EXTERNAL_IMAGES = 32; + /** * IReader constructor. */ @@ -127,6 +135,22 @@ public function getReadFilter(); */ public function setReadFilter(IReadFilter $readFilter); + /** + * Allow external images. Use with caution. + * Improper specification of these within a spreadsheet + * can subject the caller to security exploits. + * + * @param bool $allowExternalImages + * + * @return IReader + */ + public function setAllowExternalImages(bool $allowExternalImages); + + /** + * @return bool + */ + public function getAllowExternalImages(); + /** * Loads PhpSpreadsheet from file. * @@ -136,6 +160,8 @@ public function setReadFilter(IReadFilter $readFilter); * self::READ_DATA_ONLY Read only data, not style or structure information, from the file * self::SKIP_EMPTY_CELLS Don't read empty cells (cells that contain a null value, * empty string, or a string containing only whitespace characters) + * self::ALLOW_EXTERNAL_IMAGES Attempt to fetch images stored outside the spreadsheet. + * self::DONT_ALLOW_EXTERNAL_IMAGES Don't attempt to fetch images stored outside the spreadsheet. * * @return \PhpOffice\PhpSpreadsheet\Spreadsheet */ diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php index 2508154007..e688859f6e 100644 --- a/src/PhpSpreadsheet/Reader/Ods.php +++ b/src/PhpSpreadsheet/Reader/Ods.php @@ -8,6 +8,7 @@ use DOMNode; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Helper\Dimension as HelperDimension; use PhpOffice\PhpSpreadsheet\Reader\Ods\AutoFilter; use PhpOffice\PhpSpreadsheet\Reader\Ods\DefinedNames; use PhpOffice\PhpSpreadsheet\Reader\Ods\FormulaTranslator; @@ -15,7 +16,6 @@ use PhpOffice\PhpSpreadsheet\Reader\Ods\Properties as DocumentProperties; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\RichText\RichText; -use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -56,9 +56,12 @@ public function canRead(string $filename): bool $mimeType = $zip->getFromName($stat['name']); } elseif ($zip->statName('META-INF/manifest.xml')) { $xml = simplexml_load_string( - $this->getSecurityScannerOrThrow()->scan($zip->getFromName('META-INF/manifest.xml')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() + $this->getSecurityScannerOrThrow() + ->scan( + $zip->getFromName( + 'META-INF/manifest.xml' + ) + ) ); if ($xml !== false) { $namespacesContent = $xml->getNamespaces(true); @@ -99,9 +102,8 @@ public function listWorksheetNames($filename) $xml = new XMLReader(); $xml->xml( - $this->getSecurityScannerOrThrow()->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE), - null, - Settings::getLibXmlLoaderOptions() + $this->getSecurityScannerOrThrow() + ->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE) ); $xml->setParserProperty(2, true); @@ -150,9 +152,8 @@ public function listWorksheetInfo($filename) $xml = new XMLReader(); $xml->xml( - $this->getSecurityScannerOrThrow()->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE), - null, - Settings::getLibXmlLoaderOptions() + $this->getSecurityScannerOrThrow() + ->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE) ); $xml->setParserProperty(2, true); @@ -261,9 +262,8 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet) // Meta $xml = @simplexml_load_string( - $this->getSecurityScannerOrThrow()->scan($zip->getFromName('meta.xml')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() + $this->getSecurityScannerOrThrow() + ->scan($zip->getFromName('meta.xml')) ); if ($xml === false) { throw new Exception('Unable to read data from {$pFilename}'); @@ -277,8 +277,8 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet) $dom = new DOMDocument('1.01', 'UTF-8'); $dom->loadXML( - $this->getSecurityScannerOrThrow()->scan($zip->getFromName('styles.xml')), - Settings::getLibXmlLoaderOptions() + $this->getSecurityScannerOrThrow() + ->scan($zip->getFromName('styles.xml')) ); $pageSettings = new PageSettings($dom); @@ -287,19 +287,37 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet) $dom = new DOMDocument('1.01', 'UTF-8'); $dom->loadXML( - $this->getSecurityScannerOrThrow()->scan($zip->getFromName(self::INITIAL_FILE)), - Settings::getLibXmlLoaderOptions() + $this->getSecurityScannerOrThrow() + ->scan($zip->getFromName(self::INITIAL_FILE)) ); $officeNs = $dom->lookupNamespaceUri('office'); $tableNs = $dom->lookupNamespaceUri('table'); $textNs = $dom->lookupNamespaceUri('text'); $xlinkNs = $dom->lookupNamespaceUri('xlink'); + $styleNs = $dom->lookupNamespaceUri('style'); $pageSettings->readStyleCrossReferences($dom); $autoFilterReader = new AutoFilter($spreadsheet, $tableNs); $definedNameReader = new DefinedNames($spreadsheet, $tableNs); + $columnWidths = []; + $automaticStyle0 = $dom->getElementsByTagNameNS($officeNs, 'automatic-styles')->item(0); + $automaticStyles = ($automaticStyle0 === null) ? [] : $automaticStyle0->getElementsByTagNameNS($styleNs, 'style'); + foreach ($automaticStyles as $automaticStyle) { + $styleName = $automaticStyle->getAttributeNS($styleNs, 'name'); + $styleFamily = $automaticStyle->getAttributeNS($styleNs, 'family'); + if ($styleFamily === 'table-column') { + $tcprops = $automaticStyle->getElementsByTagNameNS($styleNs, 'table-column-properties'); + if ($tcprops !== null) { + $tcprop = $tcprops->item(0); + if ($tcprop !== null) { + $columnWidth = $tcprop->getAttributeNs($styleNs, 'column-width'); + $columnWidths[$styleName] = $columnWidth; + } + } + } + } // Content $item0 = $dom->getElementsByTagNameNS($officeNs, 'body')->item(0); @@ -340,6 +358,7 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet) // Go through every child of table element $rowID = 1; + $tableColumnIndex = 1; foreach ($worksheetDataSet->childNodes as $childNode) { /** @var DOMElement $childNode */ @@ -366,6 +385,26 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet) // $rowData = $cellData; // break; // } + break; + case 'table-column': + if ($childNode->hasAttributeNS($tableNs, 'number-columns-repeated')) { + $rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-columns-repeated'); + } else { + $rowRepeats = 1; + } + $tableStyleName = $childNode->getAttributeNS($tableNs, 'style-name'); + if (isset($columnWidths[$tableStyleName])) { + $columnWidth = new HelperDimension($columnWidths[$tableStyleName]); + $tableColumnString = Coordinate::stringFromColumnIndex($tableColumnIndex); + for ($rowRepeats2 = $rowRepeats; $rowRepeats2 > 0; --$rowRepeats2) { + $spreadsheet->getActiveSheet() + ->getColumnDimension($tableColumnString) + ->setWidth($columnWidth->toUnit('cm'), 'cm'); + ++$tableColumnString; + } + } + $tableColumnIndex += $rowRepeats; + break; case 'table-row': if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) { @@ -624,8 +663,8 @@ private function processSettings(ZipArchive $zip, Spreadsheet $spreadsheet): voi { $dom = new DOMDocument('1.01', 'UTF-8'); $dom->loadXML( - $this->getSecurityScannerOrThrow()->scan($zip->getFromName('settings.xml')), - Settings::getLibXmlLoaderOptions() + $this->getSecurityScannerOrThrow() + ->scan($zip->getFromName('settings.xml')) ); //$xlinkNs = $dom->lookupNamespaceUri('xlink'); $configNs = $dom->lookupNamespaceUri('config'); diff --git a/src/PhpSpreadsheet/Reader/Security/XmlScanner.php b/src/PhpSpreadsheet/Reader/Security/XmlScanner.php index f8eaf39d09..46b24c6183 100644 --- a/src/PhpSpreadsheet/Reader/Security/XmlScanner.php +++ b/src/PhpSpreadsheet/Reader/Security/XmlScanner.php @@ -6,6 +6,9 @@ class XmlScanner { + private const ENCODING_PATTERN = '/encoding\s*=\s*(["\'])(.+?)\1/s'; + private const ENCODING_UTF7 = '/encoding\s*=\s*(["\'])UTF-7\1/si'; + /** * String used to identify risky xml elements. * @@ -113,23 +116,47 @@ private static function forceString($arg): string */ private function toUtf8($xml) { - $pattern = '/encoding="(.*?)"/'; - $result = preg_match($pattern, $xml, $matches); - $charset = strtoupper($result ? $matches[1] : 'UTF-8'); - + $charset = $this->findCharSet($xml); + $foundUtf7 = $charset === 'UTF-7'; if ($charset !== 'UTF-8') { + $testStart = '/^.{0,4}\s*disableEntityLoaderCheck(); + // Don't rely purely on libxml_disable_entity_loader() + $pattern = '/\0*' . implode('\0*', /** @scrutinizer ignore-type */ str_split($this->pattern)) . '\0*/'; - $xml = $this->toUtf8($xml); + $xml = "$xml"; + if (preg_match($pattern, $xml)) { + throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); + } - // Don't rely purely on libxml_disable_entity_loader() - $pattern = '/\\0?' . implode('\\0?', /** @scrutinizer ignore-type */ str_split($this->pattern)) . '\\0?/'; + $xml = $this->toUtf8($xml); if (preg_match($pattern, $xml)) { throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); @@ -159,7 +189,7 @@ public function scan($xml) } /** - * Scan theXML for use of parseHuge = $parseHuge; + } + /** * Create a new Xlsx Reader instance. */ @@ -136,8 +148,8 @@ private function loadZip(string $filename, string $ns = '', bool $replaceUnclose } $rels = @simplexml_load_string( $this->getSecurityScannerOrThrow()->scan($contents), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions(), + SimpleXMLElement::class, + $this->parseHuge ? LIBXML_PARSEHUGE : 0, $ns ); @@ -151,8 +163,8 @@ private function loadZipNonamespace(string $filename, string $ns): SimpleXMLElem $contents = $this->getFromZipArchive($this->zip, $filename); $rels = simplexml_load_string( $this->getSecurityScannerOrThrow()->scan($contents), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions(), + SimpleXMLElement::class, + $this->parseHuge ? LIBXML_PARSEHUGE : 0, ($ns === '' ? $ns : '') ); @@ -268,11 +280,15 @@ public function listWorksheetInfo($filename) $xml = new XMLReader(); $xml->xml( - $this->getSecurityScannerOrThrow()->scan( - $this->getFromZipArchive($this->zip, $fileWorksheetPath) - ), + $this->getSecurityScannerOrThrow() + ->scan( + $this->getFromZipArchive( + $this->zip, + $fileWorksheetPath + ) + ), null, - Settings::getLibXmlLoaderOptions() + $this->parseHuge ? LIBXML_PARSEHUGE : 0, ); $xml->setParserProperty(2, true); @@ -857,6 +873,9 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet } // Read cell! + $useFormula = isset($c->f) + && ((string) $c->f !== '' || (isset($c->f->attributes()['t']) + && strtolower((string) $c->f->attributes()['t']) === 'shared')); switch ($cellDataType) { case 's': if ((string) $c->v != '') { @@ -881,10 +900,16 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet } else { // Formula $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToBoolean'); - if (isset($c->f['t'])) { - $att = $c->f; - $docSheet->getCell($r)->setFormulaAttributes($att); - } + self::storeFormulaAttributes($c->f, $docSheet, $r); + } + + break; + case 'str': + if ($useFormula) { + $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToString'); + self::storeFormulaAttributes($c->f, $docSheet, $r); + } else { + $value = self::castToString($c); } break; @@ -911,10 +936,10 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet } else { // Formula $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToString'); - if (isset($c->f['t'])) { - $attributes = $c->f['t']; - $docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]); + if (is_numeric($calculatedValue)) { + $calculatedValue += 0; } + self::storeFormulaAttributes($c->f, $docSheet, $r); } break; @@ -1291,7 +1316,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet $hfImages[$shapeId]->setName((string) $imageData['title']); } - $hfImages[$shapeId]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false); + $hfImages[$shapeId]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false, $zip); $hfImages[$shapeId]->setResizeProportional(false); $hfImages[$shapeId]->setWidth($style['width']); $hfImages[$shapeId]->setHeight($style['height']); @@ -1401,7 +1426,8 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet $objDrawing->setPath( 'zip://' . File::realpath($filename) . '#' . $images[$embedImageKey], - false + false, + $zip ); } else { $linkImageKey = (string) self::getArrayItem( @@ -1410,7 +1436,10 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet ); if (isset($images[$linkImageKey])) { $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); - $objDrawing->setPath($url); + $objDrawing->setPath($url, false, null, $this->allowExternalImages, $this->isWhitelisted); + } + if ($objDrawing->getPath() === '') { + continue; } } $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); @@ -1486,7 +1515,8 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet $objDrawing->setPath( 'zip://' . File::realpath($filename) . '#' . $images[$embedImageKey], - false + false, + $zip ); } else { $linkImageKey = (string) self::getArrayItem( @@ -1495,7 +1525,10 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet ); if (isset($images[$linkImageKey])) { $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); - $objDrawing->setPath($url); + $objDrawing->setPath($url, false, null, $this->allowExternalImages, $this->isWhitelisted); + } + if ($objDrawing->getPath() === '') { + continue; } } $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1)); @@ -1932,9 +1965,10 @@ private function readRibbon(Spreadsheet $excel, string $customUITarget, ZipArchi if ($dataRels) { // exists and not empty if the ribbon have some pictures (other than internal MSO) $UIRels = simplexml_load_string( - $this->getSecurityScannerOrThrow()->scan($dataRels), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() + $this->getSecurityScannerOrThrow() + ->scan($dataRels), + SimpleXMLElement::class, + $this->parseHuge ? LIBXML_PARSEHUGE : 0 ); if (false !== $UIRels) { // we need to save id and target to avoid parsing customUI.xml and "guess" if it's a pseudo callback who load the image @@ -2317,4 +2351,19 @@ private function processIgnoredErrors(SimpleXMLElement $xml, Worksheet $sheet): } } } + + private static function storeFormulaAttributes(SimpleXMLElement $f, Worksheet $docSheet, string $r): void + { + $formulaAttributes = []; + $attributes = $f->attributes(); + if (isset($attributes['t'])) { + $formulaAttributes['t'] = (string) $attributes['t']; + } + if (isset($attributes['ref'])) { + $formulaAttributes['ref'] = (string) $attributes['ref']; + } + if (!empty($formulaAttributes)) { + $docSheet->getCell($r)->setFormulaAttributes($formulaAttributes); + } + } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php b/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php index 2b14eab7cc..55462f7281 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\Cell\AddressRange; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter; use PhpOffice\PhpSpreadsheet\Reader\IReadFilter; @@ -196,24 +197,31 @@ private function readRowAttributes(SimpleXMLElement $worksheetRow, bool $readDat { $rowAttributes = []; + $rowIndex = 0; foreach ($worksheetRow as $rowx) { - /** @scrutinizer ignore-call */ $row = $rowx->attributes(); + ++$rowIndex; if ($row !== null) { + if (isset($row['r'])) { + $rowIndex = (int) $row['r']; + } + if ($rowIndex < 1 || $rowIndex > AddressRange::MAX_ROW) { + continue; + } if (isset($row['ht']) && !$readDataOnly) { - $rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht']; + $rowAttributes[$rowIndex]['rowHeight'] = (float) $row['ht']; } if (isset($row['hidden']) && self::boolean($row['hidden'])) { - $rowAttributes[(int) $row['r']]['visible'] = false; + $rowAttributes[$rowIndex]['visible'] = false; } if (isset($row['collapsed']) && self::boolean($row['collapsed'])) { - $rowAttributes[(int) $row['r']]['collapsed'] = true; + $rowAttributes[$rowIndex]['collapsed'] = true; } if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) { - $rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel']; + $rowAttributes[$rowIndex]['outlineLevel'] = (int) $row['outlineLevel']; } if (isset($row['s']) && !$readDataOnly) { - $rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s']; + $rowAttributes[$rowIndex]['xfIndex'] = (int) $row['s']; } } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php b/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php index dac76230cc..126db1dd43 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php @@ -22,6 +22,18 @@ public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml public function load(): void { + foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) { + // Uppercase coordinate + $range = strtoupper((string) $dataValidation['sqref']); + $rangeSet = explode(' ', $range); + foreach ($rangeSet as $range) { + if (preg_match('/^[A-Z]{1,3}\d{1,7}/', $range, $matches) === 1) { + // Ensure left/top row of range exists, thereby + // adjusting high row/column. + $this->worksheet->getCell($matches[0]); + } + } + } foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) { // Uppercase coordinate $range = strtoupper((string) $dataValidation['sqref']); diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Properties.php b/src/PhpSpreadsheet/Reader/Xlsx/Properties.php index 72addffd5b..6b23422315 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Properties.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Properties.php @@ -4,7 +4,6 @@ use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; -use PhpOffice\PhpSpreadsheet\Settings; use SimpleXMLElement; class Properties @@ -33,9 +32,7 @@ private function extractPropertyData(string $propertyData): ?SimpleXMLElement { // okay to omit namespace because everything will be processed by xpath $obj = simplexml_load_string( - $this->securityScanner->scan($propertyData), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() + $this->securityScanner->scan($propertyData) ); return self::nullOrSimple($obj); @@ -73,6 +70,9 @@ public function readExtendedProperties(string $propertyData): void if (isset($xmlCore->Manager)) { $this->docProps->setManager((string) $xmlCore->Manager); } + if (isset($xmlCore->HyperlinkBase)) { + $this->docProps->setHyperlinkBase((string) $xmlCore->HyperlinkBase); + } } } diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php index 7b8697aff2..7e0c257565 100644 --- a/src/PhpSpreadsheet/Reader/Xml.php +++ b/src/PhpSpreadsheet/Reader/Xml.php @@ -5,18 +5,18 @@ use DateTime; use DateTimeZone; use PhpOffice\PhpSpreadsheet\Cell\AddressHelper; +use PhpOffice\PhpSpreadsheet\Cell\AddressRange; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Reader\Xml\PageSettings; use PhpOffice\PhpSpreadsheet\Reader\Xml\Properties; use PhpOffice\PhpSpreadsheet\Reader\Xml\Style; use PhpOffice\PhpSpreadsheet\RichText\RichText; -use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\File; -use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; @@ -26,6 +26,8 @@ */ class Xml extends BaseReader { + public const NAMESPACES_SS = 'urn:schemas-microsoft-com:office:spreadsheet'; + /** * Formats. * @@ -74,10 +76,9 @@ public function canRead(string $filename): bool ]; // Open file - $data = file_get_contents($filename) ?: ''; - - // Why? - //$data = str_replace("'", '"', $data); // fix headers with single quote + File::assertFile($filename); + $data = (string) file_get_contents($filename); + $data = $this->getSecurityScannerOrThrow()->scan($data); $valid = true; foreach ($signature as $match) { @@ -89,14 +90,6 @@ public function canRead(string $filename): bool } } - // Retrieve charset encoding - if (preg_match('//m', $data, $matches)) { - $charSet = strtoupper($matches[1]); - if (preg_match('/^ISO-8859-\d[\dL]?$/i', $charSet) === 1) { - $data = StringHelper::convertEncoding($data, 'UTF-8', $charSet); - $data = (string) preg_replace('/()/um', '$1' . 'UTF-8' . '$2', $data, 1); - } - } $this->fileContents = $data; return $valid; @@ -113,9 +106,8 @@ public function trySimpleXMLLoadString($filename) { try { $xml = simplexml_load_string( - $this->getSecurityScannerOrThrow()->scan($this->fileContents ?: file_get_contents($filename)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() + $this->getSecurityScannerOrThrow() + ->scan($this->fileContents ?: file_get_contents($filename)) ); } catch (\Exception $e) { throw new Exception('Cannot load invalid XML file: ' . $filename, 0, $e); @@ -146,11 +138,9 @@ public function listWorksheetNames($filename) throw new Exception("Problem reading {$filename}"); } - $namespaces = $xml->getNamespaces(true); - - $xml_ss = $xml->children($namespaces['ss']); + $xml_ss = $xml->children(self::NAMESPACES_SS); foreach ($xml_ss->Worksheet as $worksheet) { - $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); + $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS); $worksheetNames[] = (string) $worksheet_ss['Name']; } @@ -178,12 +168,10 @@ public function listWorksheetInfo($filename) throw new Exception("Problem reading {$filename}"); } - $namespaces = $xml->getNamespaces(true); - $worksheetID = 1; - $xml_ss = $xml->children($namespaces['ss']); + $xml_ss = $xml->children(self::NAMESPACES_SS); foreach ($xml_ss->Worksheet as $worksheet) { - $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); + $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS); $tmpInfo = []; $tmpInfo['worksheetName'] = ''; @@ -288,12 +276,12 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo } $worksheetID = 0; - $xml_ss = $xml->children($namespaces['ss']); + $xml_ss = $xml->children(self::NAMESPACES_SS); /** @var null|SimpleXMLElement $worksheetx */ foreach ($xml_ss->Worksheet as $worksheetx) { $worksheet = $worksheetx ?? new SimpleXMLElement(''); - $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); + $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS); if ( isset($this->loadSheetsOnly, $worksheet_ss['Name']) && @@ -321,7 +309,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo // locally scoped defined names if (isset($worksheet->Names[0])) { foreach ($worksheet->Names[0] as $definedName) { - $definedName_ss = self::getAttributes($definedName, $namespaces['ss']); + $definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS); $name = (string) $definedName_ss['Name']; $definedValue = (string) $definedName_ss['RefersTo']; $convertedValue = AddressHelper::convertFormulaToA1($definedValue); @@ -335,7 +323,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo $columnID = 'A'; if (isset($worksheet->Table->Column)) { foreach ($worksheet->Table->Column as $columnData) { - $columnData_ss = self::getAttributes($columnData, $namespaces['ss']); + $columnData_ss = self::getAttributes($columnData, self::NAMESPACES_SS); $colspan = 0; if (isset($columnData_ss['Span'])) { $spanAttr = (string) $columnData_ss['Span']; @@ -367,15 +355,19 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo } } - $rowID = 1; + $rowID = 0; if (isset($worksheet->Table->Row)) { $additionalMergedCells = 0; foreach ($worksheet->Table->Row as $rowData) { $rowHasData = false; - $row_ss = self::getAttributes($rowData, $namespaces['ss']); + ++$rowID; + $row_ss = self::getAttributes($rowData, self::NAMESPACES_SS); if (isset($row_ss['Index'])) { $rowID = (int) $row_ss['Index']; } + if ($rowID < 1 || $rowID > AddressRange::MAX_ROW) { + continue; + } if (isset($row_ss['Hidden'])) { $rowVisible = ((string) $row_ss['Hidden']) !== '1'; $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setVisible($rowVisible); @@ -383,7 +375,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo $columnID = 'A'; foreach ($rowData->Cell as $cell) { - $cell_ss = self::getAttributes($cell, $namespaces['ss']); + $cell_ss = self::getAttributes($cell, self::NAMESPACES_SS); if (isset($cell_ss['Index'])) { $columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']); } @@ -425,7 +417,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo $cellData = $cell->Data; $cellValue = (string) $cellData; $type = DataType::TYPE_NULL; - $cellData_ss = self::getAttributes($cellData, $namespaces['ss']); + $cellData_ss = self::getAttributes($cellData, self::NAMESPACES_SS); if (isset($cellData_ss['Type'])) { $cellDataType = $cellData_ss['Type']; switch ($cellDataType) { @@ -483,7 +475,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo } if (isset($cell->Comment)) { - $this->parseCellComment($cell->Comment, $namespaces, $spreadsheet, $columnID, $rowID); + $this->parseCellComment($cell->Comment, $spreadsheet, $columnID, $rowID); } if (isset($cell_ss['StyleID'])) { @@ -509,50 +501,58 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight((float) $rowHeight); } } - - ++$rowID; } + } - if (isset($namespaces['x'])) { - $xmlX = $worksheet->children($namespaces['x']); - if (isset($xmlX->WorksheetOptions)) { - (new PageSettings($xmlX, $namespaces))->loadPageSettings($spreadsheet); - if (isset($xmlX->WorksheetOptions->TopRowVisible, $xmlX->WorksheetOptions->LeftColumnVisible)) { - $leftTopRow = (string) $xmlX->WorksheetOptions->TopRowVisible; - $leftTopColumn = (string) $xmlX->WorksheetOptions->LeftColumnVisible; - if (is_numeric($leftTopRow) && is_numeric($leftTopColumn)) { - $leftTopCoordinate = Coordinate::stringFromColumnIndex((int) $leftTopColumn + 1) . (string) ($leftTopRow + 1); - $spreadsheet->getActiveSheet()->setTopLeftCell($leftTopCoordinate); - } - } - $rangeCalculated = false; - if (isset($xmlX->WorksheetOptions->Panes->Pane->RangeSelection)) { - if (1 === preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $xmlX->WorksheetOptions->Panes->Pane->RangeSelection, $selectionMatches)) { - $selectedCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2]) - . $selectionMatches[1] - . ':' - . Coordinate::stringFromColumnIndex((int) $selectionMatches[4]) - . $selectionMatches[3]; - $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell); - $rangeCalculated = true; - } - } - if (!$rangeCalculated) { - if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveRow)) { - $activeRow = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveRow; - } else { - $activeRow = 0; - } - if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveCol)) { - $activeColumn = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveCol; - } else { - $activeColumn = 0; - } - if (is_numeric($activeRow) && is_numeric($activeColumn)) { - $selectedCell = Coordinate::stringFromColumnIndex((int) $activeColumn + 1) . (string) ($activeRow + 1); - $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell); - } - } + $dataValidations = new Xml\DataValidations(); + $dataValidations->loadDataValidations($worksheet, $spreadsheet); + $xmlX = $worksheet->children(Namespaces::URN_EXCEL); + if (isset($xmlX->WorksheetOptions)) { + if (isset($xmlX->WorksheetOptions->FreezePanes)) { + $freezeRow = $freezeColumn = 1; + if (isset($xmlX->WorksheetOptions->SplitHorizontal)) { + $freezeRow = (int) $xmlX->WorksheetOptions->SplitHorizontal + 1; + } + if (isset($xmlX->WorksheetOptions->SplitVertical)) { + $freezeColumn = (int) $xmlX->WorksheetOptions->SplitVertical + 1; + } + $spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow); + } + (new PageSettings($xmlX))->loadPageSettings($spreadsheet); + if (isset($xmlX->WorksheetOptions->TopRowVisible, $xmlX->WorksheetOptions->LeftColumnVisible)) { + $leftTopRow = (string) $xmlX->WorksheetOptions->TopRowVisible; + $leftTopColumn = (string) $xmlX->WorksheetOptions->LeftColumnVisible; + if (is_numeric($leftTopRow) && is_numeric($leftTopColumn)) { + $leftTopCoordinate = Coordinate::stringFromColumnIndex((int) $leftTopColumn + 1) . (string) ($leftTopRow + 1); + $spreadsheet->getActiveSheet()->setTopLeftCell($leftTopCoordinate); + } + } + $rangeCalculated = false; + if (isset($xmlX->WorksheetOptions->Panes->Pane->RangeSelection)) { + if (1 === preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $xmlX->WorksheetOptions->Panes->Pane->RangeSelection, $selectionMatches)) { + $selectedCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2]) + . $selectionMatches[1] + . ':' + . Coordinate::stringFromColumnIndex((int) $selectionMatches[4]) + . $selectionMatches[3]; + $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell); + $rangeCalculated = true; + } + } + if (!$rangeCalculated) { + if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveRow)) { + $activeRow = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveRow; + } else { + $activeRow = 0; + } + if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveCol)) { + $activeColumn = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveCol; + } else { + $activeColumn = 0; + } + if (is_numeric($activeRow) && is_numeric($activeColumn)) { + $selectedCell = Coordinate::stringFromColumnIndex((int) $activeColumn + 1) . (string) ($activeRow + 1); + $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell); } } } @@ -567,7 +567,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo $activeWorksheet = $spreadsheet->setActiveSheetIndex($activeSheetIndex); if (isset($xml->Names[0])) { foreach ($xml->Names[0] as $definedName) { - $definedName_ss = self::getAttributes($definedName, $namespaces['ss']); + $definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS); $name = (string) $definedName_ss['Name']; $definedValue = (string) $definedName_ss['RefersTo']; $convertedValue = AddressHelper::convertFormulaToA1($definedValue); @@ -584,12 +584,11 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo protected function parseCellComment( SimpleXMLElement $comment, - array $namespaces, Spreadsheet $spreadsheet, string $columnID, int $rowID ): void { - $commentAttributes = $comment->attributes($namespaces['ss']); + $commentAttributes = $comment->attributes(self::NAMESPACES_SS); $author = 'unknown'; if (isset($commentAttributes->Author)) { $author = (string) $commentAttributes->Author; diff --git a/src/PhpSpreadsheet/Reader/Xml/DataValidations.php b/src/PhpSpreadsheet/Reader/Xml/DataValidations.php new file mode 100644 index 0000000000..31748cb9c2 --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xml/DataValidations.php @@ -0,0 +1,177 @@ + DataValidation::OPERATOR_BETWEEN, + 'equal' => DataValidation::OPERATOR_EQUAL, + 'greater' => DataValidation::OPERATOR_GREATERTHAN, + 'greaterorequal' => DataValidation::OPERATOR_GREATERTHANOREQUAL, + 'less' => DataValidation::OPERATOR_LESSTHAN, + 'lessorequal' => DataValidation::OPERATOR_LESSTHANOREQUAL, + 'notbetween' => DataValidation::OPERATOR_NOTBETWEEN, + 'notequal' => DataValidation::OPERATOR_NOTEQUAL, + ]; + + private const TYPE_MAPPINGS = [ + 'textlength' => DataValidation::TYPE_TEXTLENGTH, + ]; + + private int $thisRow = 0; + + private int $thisColumn = 0; + + private function replaceR1C1(array $matches): string + { + return AddressHelper::convertToA1($matches[0], $this->thisRow, $this->thisColumn, false); + } + + public function loadDataValidations(SimpleXMLElement $worksheet, Spreadsheet $spreadsheet): void + { + $xmlX = $worksheet->children(Namespaces::URN_EXCEL); + $sheet = $spreadsheet->getActiveSheet(); + /** @var callable */ + $pregCallback = [$this, 'replaceR1C1']; + foreach ($xmlX->DataValidation as $dataValidation) { + $cells = []; + $validation = new DataValidation(); + + // set defaults + $validation->setShowDropDown(true); + $validation->setShowInputMessage(true); + $validation->setShowErrorMessage(true); + $validation->setShowDropDown(true); + $this->thisRow = 1; + $this->thisColumn = 1; + + foreach ($dataValidation as $tagName => $tagValue) { + $tagValue = (string) $tagValue; + $tagValueLower = strtolower($tagValue); + switch ($tagName) { + case 'Range': + foreach (explode(',', $tagValue) as $range) { + $cell = ''; + if (preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) { + // range + $firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2]) + . $selectionMatches[1]; + $cell = $firstCell + . ':' + . Coordinate::stringFromColumnIndex((int) $selectionMatches[4]) + . $selectionMatches[3]; + $this->thisRow = (int) $selectionMatches[1]; + $this->thisColumn = (int) $selectionMatches[2]; + $sheet->getCell($firstCell); + } elseif (preg_match('/^R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) { + // cell + $cell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2]) + . $selectionMatches[1]; + $sheet->getCell($cell); + $this->thisRow = (int) $selectionMatches[1]; + $this->thisColumn = (int) $selectionMatches[2]; + } elseif (preg_match('/^C(\d+)$/', (string) $range, $selectionMatches) === 1) { + // column + $firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[1]) + . '1'; + $cell = $firstCell + . ':' + . Coordinate::stringFromColumnIndex((int) $selectionMatches[1]) + . ((string) AddressRange::MAX_ROW); + $this->thisColumn = (int) $selectionMatches[1]; + $sheet->getCell($firstCell); + } elseif (preg_match('/^R(\d+)$/', (string) $range, $selectionMatches)) { + // row + $firstCell = 'A' + . $selectionMatches[1]; + $cell = $firstCell + . ':' + . AddressRange::MAX_COLUMN + . $selectionMatches[1]; + $this->thisRow = (int) $selectionMatches[1]; + $sheet->getCell($firstCell); + } + + $validation->setSqref($cell); + $stRange = $sheet->shrinkRangeToFit($cell); + $cells = array_merge($cells, Coordinate::extractAllCellReferencesInRange($stRange)); + } + + break; + case 'Type': + $validation->setType(self::TYPE_MAPPINGS[$tagValueLower] ?? $tagValueLower); + + break; + case 'Qualifier': + $validation->setOperator(self::OPERATOR_MAPPINGS[$tagValueLower] ?? $tagValueLower); + + break; + case 'InputTitle': + $validation->setPromptTitle($tagValue); + + break; + case 'InputMessage': + $validation->setPrompt($tagValue); + + break; + case 'InputHide': + $validation->setShowInputMessage(false); + + break; + case 'ErrorStyle': + $validation->setErrorStyle($tagValueLower); + + break; + case 'ErrorTitle': + $validation->setErrorTitle($tagValue); + + break; + case 'ErrorMessage': + $validation->setError($tagValue); + + break; + case 'ErrorHide': + $validation->setShowErrorMessage(false); + + break; + case 'ComboHide': + $validation->setShowDropDown(false); + + break; + case 'UseBlank': + $validation->setAllowBlank(true); + + break; + case 'CellRangeList': + // FIXME missing FIXME + + break; + case 'Min': + case 'Value': + $tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue); + $validation->setFormula1($tagValue); + + break; + case 'Max': + $tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue); + $validation->setFormula2($tagValue); + + break; + } + } + + foreach ($cells as $cell) { + $sheet->getCell($cell)->setDataValidation(clone $validation); + } + } + } +} diff --git a/src/PhpSpreadsheet/Reader/Xml/PageSettings.php b/src/PhpSpreadsheet/Reader/Xml/PageSettings.php index 39535c3e71..137cabaf30 100644 --- a/src/PhpSpreadsheet/Reader/Xml/PageSettings.php +++ b/src/PhpSpreadsheet/Reader/Xml/PageSettings.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xml; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; use SimpleXMLElement; @@ -14,9 +15,9 @@ class PageSettings */ private $printSettings; - public function __construct(SimpleXMLElement $xmlX, array $namespaces) + public function __construct(SimpleXMLElement $xmlX) { - $printSettings = $this->pageSetup($xmlX, $namespaces, $this->getPrintDefaults()); + $printSettings = $this->pageSetup($xmlX, $this->getPrintDefaults()); $this->printSettings = $this->printSetup($xmlX, $printSettings); } @@ -56,13 +57,13 @@ private function getPrintDefaults(): stdClass ]; } - private function pageSetup(SimpleXMLElement $xmlX, array $namespaces, stdClass $printDefaults): stdClass + private function pageSetup(SimpleXMLElement $xmlX, stdClass $printDefaults): stdClass { if (isset($xmlX->WorksheetOptions->PageSetup)) { foreach ($xmlX->WorksheetOptions->PageSetup as $pageSetupData) { foreach ($pageSetupData as $pageSetupKey => $pageSetupValue) { /** @scrutinizer ignore-call */ - $pageSetupAttributes = $pageSetupValue->attributes($namespaces['x']); + $pageSetupAttributes = $pageSetupValue->attributes(Namespaces::URN_EXCEL); if ($pageSetupAttributes !== null) { switch ($pageSetupKey) { case 'Layout': diff --git a/src/PhpSpreadsheet/Reader/Xml/Properties.php b/src/PhpSpreadsheet/Reader/Xml/Properties.php index f0346ed02f..e216c254da 100644 --- a/src/PhpSpreadsheet/Reader/Xml/Properties.php +++ b/src/PhpSpreadsheet/Reader/Xml/Properties.php @@ -92,6 +92,10 @@ protected function processStandardProperty( case 'Manager': $docProps->setManager($stringValue); + break; + case 'HyperlinkBase': + $docProps->setHyperlinkBase($stringValue); + break; case 'Keywords': $docProps->setKeywords($stringValue); @@ -110,17 +114,10 @@ protected function processCustomProperty( ?SimpleXMLElement $propertyValue, SimpleXMLElement $propertyAttributes ): void { - $propertyType = DocumentProperties::PROPERTY_TYPE_UNKNOWN; - switch ((string) $propertyAttributes) { - case 'string': - $propertyType = DocumentProperties::PROPERTY_TYPE_STRING; - $propertyValue = trim((string) $propertyValue); - - break; case 'boolean': $propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN; - $propertyValue = (bool) $propertyValue; + $propertyValue = (bool) (string) $propertyValue; break; case 'integer': @@ -134,9 +131,15 @@ protected function processCustomProperty( break; case 'dateTime.tz': + case 'dateTime.iso8601tz': $propertyType = DocumentProperties::PROPERTY_TYPE_DATE; $propertyValue = trim((string) $propertyValue); + break; + default: + $propertyType = DocumentProperties::PROPERTY_TYPE_STRING; + $propertyValue = trim((string) $propertyValue); + break; } diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index 90eee534db..8cd0c6de45 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -913,7 +913,7 @@ private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet { $cellAddress = $definedName->getValue(); $asFormula = ($cellAddress[0] === '='); - if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) { + if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashInt() === $worksheet->getHashInt()) { /** * If we delete the entire range that is referenced by a Named Range, MS Excel sets the value to #REF! * PhpSpreadsheet still only does a basic adjustment, so the Named Range will still reference Cells. @@ -932,7 +932,7 @@ private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void { - if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) { + if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashInt() === $worksheet->getHashInt()) { /** * If we delete the entire range that is referenced by a Named Formula, MS Excel sets the value to #REF! * PhpSpreadsheet still only does a basic adjustment, so the Named Formula will still reference Cells. diff --git a/src/PhpSpreadsheet/Settings.php b/src/PhpSpreadsheet/Settings.php index d8007fd009..edd9ca2c6a 100644 --- a/src/PhpSpreadsheet/Settings.php +++ b/src/PhpSpreadsheet/Settings.php @@ -5,8 +5,6 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Chart\Renderer\IRenderer; use PhpOffice\PhpSpreadsheet\Collection\Memory; -use Psr\Http\Client\ClientInterface; -use Psr\Http\Message\RequestFactoryInterface; use Psr\SimpleCache\CacheInterface; use ReflectionClass; @@ -37,12 +35,12 @@ class Settings /** * The HTTP client implementation to be used for network request. * - * @var null|ClientInterface + * @var mixed */ private static $httpClient; /** - * @var null|RequestFactoryInterface + * @var mixed */ private static $requestFactory; @@ -98,6 +96,8 @@ public static function htmlEntityFlags(): int * Set default options for libxml loader. * * @param ?int $options Default options for libxml loader + * + * @deprecated 3.5.0 no longer needed */ public static function setLibXmlLoaderOptions($options): int { @@ -114,14 +114,12 @@ public static function setLibXmlLoaderOptions($options): int * Defaults to LIBXML_DTDLOAD | LIBXML_DTDATTR when not set explicitly. * * @return int Default options for libxml loader + * + * @deprecated 3.5.0 no longer needed */ public static function getLibXmlLoaderOptions(): int { - if (self::$libXmlLoaderOptions === null) { - return self::setLibXmlLoaderOptions(null); - } - - return self::$libXmlLoaderOptions; + return self::$libXmlLoaderOptions ?? (defined('LIBXML_DTDLOAD') ? (LIBXML_DTDLOAD | LIBXML_DTDATTR) : 0); } /** @@ -181,8 +179,13 @@ public static function useSimpleCacheVersion3(): bool /** * Set the HTTP client implementation to be used for network request. + * + * @param mixed $httpClient + * @param mixed $requestFactory + * + * @deprecated 1.30.2 No replacement. */ - public static function setHttpClient(ClientInterface $httpClient, RequestFactoryInterface $requestFactory): void + public static function setHttpClient($httpClient, $requestFactory): void { self::$httpClient = $httpClient; self::$requestFactory = $requestFactory; @@ -190,6 +193,8 @@ public static function setHttpClient(ClientInterface $httpClient, RequestFactory /** * Unset the HTTP client configuration. + * + * @deprecated 1.30.2 No replacement. */ public static function unsetHttpClient(): void { @@ -199,25 +204,25 @@ public static function unsetHttpClient(): void /** * Get the HTTP client implementation to be used for network request. + * + * @return mixed + * + * @deprecated 1.30.2 No replacement. */ - public static function getHttpClient(): ClientInterface + public static function getHttpClient() { - if (!self::$httpClient || !self::$requestFactory) { - throw new Exception('HTTP client must be configured via Settings::setHttpClient() to be able to use WEBSERVICE function.'); - } - return self::$httpClient; } /** * Get the HTTP request factory. + * + * @return mixed + * + * @deprecated 1.30.2 No replacement. */ - public static function getRequestFactory(): RequestFactoryInterface + public static function getRequestFactory() { - if (!self::$httpClient || !self::$requestFactory) { - throw new Exception('HTTP client must be configured via Settings::setHttpClient() to be able to use WEBSERVICE function.'); - } - return self::$requestFactory; } } diff --git a/src/PhpSpreadsheet/Shared/Date.php b/src/PhpSpreadsheet/Shared/Date.php index 26dde93f3a..6ded02a966 100644 --- a/src/PhpSpreadsheet/Shared/Date.php +++ b/src/PhpSpreadsheet/Shared/Date.php @@ -184,7 +184,7 @@ public static function convertIsoDate($value) throw new Exception("Invalid string $value supplied for datatype Date"); } - if (preg_match('/^\\d\\d:\\d\\d:\\d\\d/', $value) == 1) { + if (preg_match('/^\s*\d?\d:\d\d(:\d\d([.]\d+)?)?\s*(am|pm)?\s*$/i', $value) == 1) { $newValue = fmod($newValue, 1.0); } diff --git a/src/PhpSpreadsheet/Shared/File.php b/src/PhpSpreadsheet/Shared/File.php index 737a6eb591..a4860a1e10 100644 --- a/src/PhpSpreadsheet/Shared/File.php +++ b/src/PhpSpreadsheet/Shared/File.php @@ -140,11 +140,30 @@ public static function temporaryFilename(): string return $filename; } + /** + * All filenames starting with protocol (e.g. phar://) are prohibited. + * Note that many protocols, including http and zip, will already + * return false for is_file. + * A whitelist of protocols may be added if needed in future. + */ + public static function prohibitWrappers(string $filename): void + { + $scheme = parse_url($filename, PHP_URL_SCHEME); + // strlen check > 1 to avoid issues with Windows absolute paths (e.g. C:\...), Windows quirks :) + // since no built-in or commonly registered PHP stream wrapper uses a single-character scheme, this should be ok, to my knowledge + if (is_string($scheme) && strlen($scheme) > 1) { + throw new Exception( + "Stream wrappers are not permitted as file paths: {$filename}" + ); + } + } + /** * Assert that given path is an existing file and is readable, otherwise throw exception. */ public static function assertFile(string $filename, string $zipMember = ''): void { + self::prohibitWrappers($filename); if (!is_file($filename)) { throw new ReaderException('File "' . $filename . '" does not exist.'); } @@ -167,9 +186,11 @@ public static function assertFile(string $filename, string $zipMember = ''): voi /** * Same as assertFile, except return true/false and don't throw Exception. + * Will nevertheless throw if filename uses invalid protocol, e.g. phar. */ public static function testFileNoThrow(string $filename, ?string $zipMember = null): bool { + self::prohibitWrappers($filename); if (!is_file($filename)) { return false; } diff --git a/src/PhpSpreadsheet/Shared/Font.php b/src/PhpSpreadsheet/Shared/Font.php index 90c1992a3a..b32910789a 100644 --- a/src/PhpSpreadsheet/Shared/Font.php +++ b/src/PhpSpreadsheet/Shared/Font.php @@ -561,7 +561,7 @@ public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkP if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') { $separator = DIRECTORY_SEPARATOR; } - $fontFileAbsolute = preg_match('~^([A-Za-z]:)?[/\\\\]~', $fontFile) === 1; + $fontFileAbsolute = preg_match('~^([A-Za-z]:)?[/\\\]~', $fontFile) === 1; if (!$fontFileAbsolute) { $fontFile = self::$trueTypeFontPath . $separator . $fontFile; } diff --git a/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php b/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php index bce5fe34a4..5d5babc3f4 100644 --- a/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php +++ b/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php @@ -163,7 +163,7 @@ public function stream_seek($offset, $whence) // @codingStandardsIgnoreLine $this->pos = $offset; } elseif ($whence == SEEK_CUR && -$offset <= $this->pos) { $this->pos += $offset; - // @phpstan-ignore-next-line + // @phpstan-ignore-next-line } elseif ($whence == SEEK_END && -$offset <= count(/** @scrutinizer ignore-type */ $this->data)) { $this->pos = strlen($this->data) + $offset; } else { diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php index 110908550f..9b1e1e3e13 100644 --- a/src/PhpSpreadsheet/Spreadsheet.php +++ b/src/PhpSpreadsheet/Spreadsheet.php @@ -4,13 +4,10 @@ use JsonSerializable; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; -use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\Iterator; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; -use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter; class Spreadsheet implements JsonSerializable { @@ -599,7 +596,7 @@ public function getActiveSheet() public function createSheet($sheetIndex = null) { $newSheet = new Worksheet($this); - $this->addSheet($newSheet, $sheetIndex); + $this->addSheet($newSheet, $sheetIndex, true); return $newSheet; } @@ -621,11 +618,24 @@ public function sheetNameExists($worksheetName) * * @param Worksheet $worksheet The worksheet to add * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last) + * @param bool $retitleIfNeeded add suffix if title exists in spreadsheet * * @return Worksheet */ - public function addSheet(Worksheet $worksheet, $sheetIndex = null) + public function addSheet(Worksheet $worksheet, $sheetIndex = null, $retitleIfNeeded = false) { + if ($retitleIfNeeded) { + $title = $worksheet->getTitle(); + if ($this->sheetNameExists($title)) { + $i = 1; + $newTitle = "$title $i"; + while ($this->sheetNameExists($newTitle)) { + ++$i; + $newTitle = "$title $i"; + } + $worksheet->setTitle($newTitle); + } + } if ($this->sheetNameExists($worksheet->getTitle())) { throw new Exception( "Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename this worksheet first." @@ -750,13 +760,17 @@ public function getSheetByNameOrThrow(string $worksheetName): Worksheet * * @return int index */ - public function getIndex(Worksheet $worksheet) + public function getIndex(Worksheet $worksheet, bool $noThrow = false) { + $wsHash = $worksheet->getHashInt(); foreach ($this->workSheetCollection as $key => $value) { - if ($value->getHashCode() === $worksheet->getHashCode()) { + if ($value->getHashInt() === $wsHash) { return $key; } } + if ($noThrow) { + return -1; + } throw new Exception('Sheet does not exist.'); } @@ -1144,17 +1158,7 @@ public function getWorksheetIterator() */ public function copy() { - $filename = File::temporaryFilename(); - $writer = new XlsxWriter($this); - $writer->setIncludeCharts(true); - $writer->save($filename); - - $reader = new XlsxReader(); - $reader->setIncludeCharts(true); - $reloadedSpreadsheet = $reader->load($filename); - unlink($filename); - - return $reloadedSpreadsheet; + return unserialize(serialize($this)); } public function __clone() @@ -1646,16 +1650,6 @@ public function getSharedComponent(): Style return new Style(); } - /** - * @throws Exception - * - * @return mixed - */ - public function __serialize() - { - throw new Exception('Spreadsheet objects cannot be serialized'); - } - /** * @throws Exception */ @@ -1685,4 +1679,25 @@ public function resetThemeFonts(): void } } } + + /** @var string[] */ + private $domainWhiteList = []; + + /** + * Currently used only by WEBSERVICE function. + * + * @param string[] $domainWhiteList + */ + public function setDomainWhiteList(array $domainWhiteList): self + { + $this->domainWhiteList = $domainWhiteList; + + return $this; + } + + /** @return string[] */ + public function getDomainWhiteList(): array + { + return $this->domainWhiteList; + } } diff --git a/src/PhpSpreadsheet/Style/Color.php b/src/PhpSpreadsheet/Style/Color.php index 3c002b2705..282defc0ce 100644 --- a/src/PhpSpreadsheet/Style/Color.php +++ b/src/PhpSpreadsheet/Style/Color.php @@ -362,23 +362,8 @@ public static function changeBrightness($hexColourValue, $adjustPercentage) $green = self::getGreen($hexColourValue, false); /** @var int $blue */ $blue = self::getBlue($hexColourValue, false); - if ($adjustPercentage > 0) { - $red += (255 - $red) * $adjustPercentage; - $green += (255 - $green) * $adjustPercentage; - $blue += (255 - $blue) * $adjustPercentage; - } else { - $red += $red * $adjustPercentage; - $green += $green * $adjustPercentage; - $blue += $blue * $adjustPercentage; - } - - $rgb = strtoupper( - str_pad(dechex((int) $red), 2, '0', 0) . - str_pad(dechex((int) $green), 2, '0', 0) . - str_pad(dechex((int) $blue), 2, '0', 0) - ); - return (($rgba) ? 'FF' : '') . $rgb; + return (($rgba) ? 'FF' : '') . RgbTint::rgbAndTintToRgb($red, $green, $blue, $adjustPercentage); } /** diff --git a/src/PhpSpreadsheet/Style/Conditional.php b/src/PhpSpreadsheet/Style/Conditional.php index de565d3458..36069b00c8 100644 --- a/src/PhpSpreadsheet/Style/Conditional.php +++ b/src/PhpSpreadsheet/Style/Conditional.php @@ -248,7 +248,7 @@ public function getConditions() /** * Set Conditions. * - * @param bool|float|int|string|(bool|float|int|string)[] $conditions Condition + * @param (bool|float|int|string)[]|bool|float|int|string $conditions Condition * * @return $this */ diff --git a/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php b/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php index ba54b53593..aeecf12f93 100644 --- a/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php +++ b/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php @@ -165,7 +165,7 @@ public static function format($value, string $format): string // If the colon preceding minute had been quoted, as happens in // Excel 2003 XML formats, m will not have been changed to i above. // Change it now. - $format = (string) \preg_replace('/\\\\:m/', ':i', $format); + $format = (string) \preg_replace('/\\\:m/', ':i', $format); return $dateObj->format($format); } diff --git a/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php b/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php index 41a17151d3..7fc30491c9 100644 --- a/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php +++ b/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php @@ -14,6 +14,7 @@ class Formatter * Matches any @ symbol that isn't enclosed in quotes. */ private const SYMBOL_AT = '/@(?=(?:[^"]*"[^"]*")*[^"]*\Z)/miu'; + private const QUOTE_REPLACEMENT = "\u{fffe}"; // invalid Unicode character /** * Matches any ; symbol that isn't enclosed in quotes, for a "section" split. @@ -69,8 +70,8 @@ private static function splitFormatForSectionSelection(array $sections, $value): // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT] $sectionCount = count($sections); // Colour could be a named colour, or a numeric index entry in the colour-palette - $color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . '|color\\s*(\\d+))\\]/mui'; - $cond_regex = '/\\[(>|>=|<|<=|=|<>)([+-]?\\d+([.]\\d+)?)\\]/'; + $color_regex = '/\[(' . implode('|', Color::NAMED_COLORS) . '|color\s*(\d+))\]/mui'; + $cond_regex = '/\[(>|>=|<|<=|=|<>)([+-]?\d+([.]\d+)?)\]/'; $colors = ['', '', '', '', '']; $conditionOperations = ['', '', '', '', '']; $conditionComparisonValues = [0, 0, 0, 0, 0]; @@ -137,8 +138,33 @@ public static function toFormattedString($value, $format, $callBack = null) } // For now we do not treat strings in sections, although section 4 of a format code affects strings // Process a single block format code containing @ for text substitution - if (preg_match(self::SECTION_SPLIT, $format) === 0 && preg_match(self::SYMBOL_AT, $format) === 1) { - return str_replace('"', '', preg_replace(self::SYMBOL_AT, (string) $value, $format) ?? ''); + $formatx = str_replace('\"', self::QUOTE_REPLACEMENT, $format); + if (preg_match(self::SECTION_SPLIT, $format) === 0 && preg_match(self::SYMBOL_AT, $formatx) === 1) { + if (strpos($format, '"') === false) { + $temp = str_replace('@', "$value", $format); + if (is_callable($callBack)) { + $temp = $callBack($temp, $format); + } + + return $temp; + } + //escape any dollar signs on the string, so they are not replaced with an empty value + $value = str_replace( + ['$', '"'], + ['\$', self::QUOTE_REPLACEMENT], + (string) $value + ); + $temp = preg_replace(self::SYMBOL_AT, $value, $formatx) ?? $value; + if (is_callable($callBack)) { + $temp = $callBack($temp, $formatx); + } + /** @var string $temp */ + + return str_replace( + ['"', self::QUOTE_REPLACEMENT], + ['', '"'], + $temp + ); } // If we have a text value, return it "as is" @@ -156,7 +182,7 @@ public static function toFormattedString($value, $format, $callBack = null) $format = (string) preg_replace('/^\[\$-[^\]]*\]/', '', $format); $format = (string) preg_replace_callback( - '/(["])(?:(?=(\\\\?))\\2.)*?\\1/u', + '/(["])(?:(?=(\\\?))\2.)*?\1/u', function ($matches) { return str_replace('.', chr(0x00), $matches[0]); }, diff --git a/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php b/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php index fdcf98329b..807832f7dd 100644 --- a/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php +++ b/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php @@ -63,7 +63,7 @@ public static function format($value, string $format): string private static function getDecimal(string $value): string { $decimalPart = '0'; - if (preg_match('/^\\d*[.](\\d*[1-9])0*$/', $value, $matches) === 1) { + if (preg_match('/^\d*[.](\d*[1-9])0*$/', $value, $matches) === 1) { $decimalPart = $matches[1]; } diff --git a/src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php b/src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php index c035345865..19c6b19fa9 100644 --- a/src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php +++ b/src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php @@ -7,7 +7,7 @@ class NumberFormatter { - private const NUMBER_REGEX = '/(0+)(\\.?)(0*)/'; + private const NUMBER_REGEX = '/(0+)(\.?)(0*)/'; private static function mergeComplexNumberFormatMasks(array $numbers, array $masks): array { @@ -223,11 +223,11 @@ public static function format($value, string $format): string $paddingPlaceholder = (strpos($format, '?') !== false); // Replace # or ? with 0 - $format = self::pregReplace('/[\\#\?](?=(?:[^"]*"[^"]*")*[^"]*\Z)/', '0', $format); + $format = self::pregReplace('/[\#\?](?=(?:[^"]*"[^"]*")*[^"]*\Z)/', '0', $format); // Remove locale code [$-###] for an LCID $format = self::pregReplace('/\[\$\-.*\]/', '', $format); - $n = '/\\[[^\\]]+\\]/'; + $n = '/\[[^\]]+\]/'; $m = self::pregReplace($n, '', $format); // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols diff --git a/src/PhpSpreadsheet/Style/RgbTint.php b/src/PhpSpreadsheet/Style/RgbTint.php new file mode 100644 index 0000000000..582ae48397 --- /dev/null +++ b/src/PhpSpreadsheet/Style/RgbTint.php @@ -0,0 +1,175 @@ += 0.0) ? $hue : (1.0 + $hue); + } + + /** + * Convert red/green/blue to HLSMAX-based hue/luminance/saturation. + * + * @return int[] + */ + private static function rgbToMsHls(int $red, int $green, int $blue): array + { + $red01 = $red / self::RGBMAX; + $green01 = $green / self::RGBMAX; + $blue01 = $blue / self::RGBMAX; + [$hue, $luminance, $saturation] = self::rgbToHls($red01, $green01, $blue01); + + return [ + (int) round($hue * self::HLSMAX), + (int) round($luminance * self::HLSMAX), + (int) round($saturation * self::HLSMAX), + ]; + } + + /** + * Converts HLSMAX based HLS values to rgb values in the range (0,1). + * + * @return float[] + */ + private static function msHlsToRgb(int $hue, int $lightness, int $saturation): array + { + return self::hlsToRgb($hue / self::HLSMAX, $lightness / self::HLSMAX, $saturation / self::HLSMAX); + } + + /** + * Tints HLSMAX based luminance. + * + * @see http://ciintelligence.blogspot.co.uk/2012/02/converting-excel-theme-color-and-tint.html + */ + private static function tintLuminance(float $tint, float $luminance): int + { + if ($tint < 0) { + return (int) round($luminance * (1.0 + $tint)); + } + + return (int) round($luminance * (1.0 - $tint) + (self::HLSMAX - self::HLSMAX * (1.0 - $tint))); + } + + /** + * Return result of tinting supplied rgb as 6 hex digits. + */ + public static function rgbAndTintToRgb(int $red, int $green, int $blue, float $tint): string + { + [$hue, $luminance, $saturation] = self::rgbToMsHls($red, $green, $blue); + [$red, $green, $blue] = self::msHlsToRgb($hue, self::tintLuminance($tint, $luminance), $saturation); + + return sprintf( + '%02X%02X%02X', + (int) round($red * self::RGBMAX), + (int) round($green * self::RGBMAX), + (int) round($blue * self::RGBMAX) + ); + } +} diff --git a/src/PhpSpreadsheet/Worksheet/AutoFilter.php b/src/PhpSpreadsheet/Worksheet/AutoFilter.php index cbc4ff65fe..c7eeff3c59 100644 --- a/src/PhpSpreadsheet/Worksheet/AutoFilter.php +++ b/src/PhpSpreadsheet/Worksheet/AutoFilter.php @@ -146,7 +146,7 @@ public function setRangeToMaxRow(): self $this->evaluated = false; if ($this->workSheet !== null) { $thisrange = $this->range; - $range = (string) preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange); + $range = (string) preg_replace('/\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange); if ($range !== $thisrange) { $this->setRange($range); } diff --git a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php index 5001346ad5..6396ac7c62 100644 --- a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php +++ b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php @@ -219,15 +219,18 @@ public function getWorksheet(): ?Worksheet public function setWorksheet(?Worksheet $worksheet = null, bool $overrideOld = false): self { if ($this->worksheet === null) { - // Add drawing to \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet + // Add drawing to Worksheet if ($worksheet !== null) { $this->worksheet = $worksheet; - $this->worksheet->getCell($this->coordinates); - $this->worksheet->getDrawingCollection()->append($this); + if (!($this instanceof Drawing && $this->getPath() === '')) { + $this->worksheet->getCell($this->coordinates); + } + $this->worksheet->getDrawingCollection() + ->append($this); } } else { if ($overrideOld) { - // Remove drawing from old \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet + // Remove drawing from old Worksheet $iterator = $this->worksheet->getDrawingCollection()->getIterator(); while ($iterator->valid()) { @@ -239,10 +242,10 @@ public function setWorksheet(?Worksheet $worksheet = null, bool $overrideOld = f } } - // Set new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet + // Set new Worksheet $this->setWorksheet($worksheet); } else { - throw new PhpSpreadsheetException('A Worksheet has already been assigned. Drawings can only exist on one \\PhpOffice\\PhpSpreadsheet\\Worksheet.'); + throw new PhpSpreadsheetException('A Worksheet has already been assigned. Drawings can only exist on one Worksheet.'); } } @@ -257,6 +260,11 @@ public function getCoordinates(): string public function setCoordinates(string $coordinates): self { $this->coordinates = $coordinates; + if ($this->worksheet !== null) { + if (!($this instanceof Drawing && $this->getPath() === '')) { + $this->worksheet->getCell($this->coordinates); + } + } return $this; } @@ -436,7 +444,7 @@ public function getHashCode() return md5( $this->name . $this->description . - (($this->worksheet === null) ? '' : $this->worksheet->getHashCode()) . + (($this->worksheet === null) ? '' : (string) $this->worksheet->getHashInt()) . $this->coordinates . $this->offsetX . $this->offsetY . diff --git a/src/PhpSpreadsheet/Worksheet/Drawing.php b/src/PhpSpreadsheet/Worksheet/Drawing.php index 7d957539ef..0fbba173fa 100644 --- a/src/PhpSpreadsheet/Worksheet/Drawing.php +++ b/src/PhpSpreadsheet/Worksheet/Drawing.php @@ -101,45 +101,109 @@ public function getPath() * @param string $path File path * @param bool $verifyFile Verify file * @param ZipArchive $zip Zip archive instance + * @param bool $allowExternal + * @param null|callable(string):bool $isWhitelisted * * @return $this */ - public function setPath($path, $verifyFile = true, $zip = null) + public function setPath($path, $verifyFile = true, $zip = null, $allowExternal = true, ?callable $isWhitelisted = null) { - if ($verifyFile && preg_match('~^data:image/[a-z]+;base64,~', $path) !== 1) { - // Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979 - if (filter_var($path, FILTER_VALIDATE_URL)) { - $this->path = $path; - // Implicit that it is a URL, rather store info than running check above on value in other places. - $this->isUrl = true; - $imageContents = file_get_contents($path); + $this->isUrl = false; + if (preg_match('~^data:image/[a-z]+;base64,~', $path) === 1) { + $this->path = $path; + + return $this; + } + + $this->path = ''; + if ($zip instanceof ZipArchive) { + $zipPath = explode('#', $path)[1]; + $locate = @$zip->locateName($zipPath); + if ($locate !== false) { + if ($this->isImage($path)) { + $this->path = $path; + $this->setSizesAndType($path); + } + } + // Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979 + } elseif (filter_var($path, FILTER_VALIDATE_URL) || (preg_match('/^([\w\s\x00-\x1f]+):/u', $path) && !preg_match('/^([\w]+):/u', $path))) { + if (!preg_match('/^(http|https|file|ftp|s3):/', $path)) { + throw new PhpSpreadsheetException('Invalid protocol for linked drawing'); + } + if (!$allowExternal) { + return $this; + } + if ($isWhitelisted !== null && !$isWhitelisted($path)) { + return $this; + } + // Implicit that it is a URL, rather store info than running check above on value in other places. + $this->isUrl = true; + $ctx = null; + // https://github.com/php/php-src/issues/16023 + // https://github.com/php/php-src/issues/17121 + if (preg_match('/^https?:/', $path) === 1) { + $ctxArray = [ + 'http' => [ + 'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + 'header' => [ + //'Connection: keep-alive', // unacceptable performance + 'Accept: image/*;q=0.9,*/*;q=0.8', + ], + ], + ]; + if (preg_match('/^https:/', $path) === 1) { + $ctxArray['ssl'] = ['crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT]; + } + $ctx = stream_context_create($ctxArray); + } + $imageContents = @file_get_contents($path, false, $ctx); + if ($imageContents !== false) { $filePath = tempnam(sys_get_temp_dir(), 'Drawing'); if ($filePath) { - file_put_contents($filePath, $imageContents); - if (file_exists($filePath)) { - $this->setSizesAndType($filePath); + $put = @file_put_contents($filePath, $imageContents); + if ($put !== false) { + if ($this->isImage($filePath)) { + $this->path = $path; + $this->setSizesAndType($filePath); + } unlink($filePath); } } - } elseif (file_exists($path)) { + } + } else { + $exists = @file_exists($path); + if ($exists !== false && $this->isImage($path)) { $this->path = $path; $this->setSizesAndType($path); - } elseif ($zip instanceof ZipArchive) { - $zipPath = explode('#', $path)[1]; - if ($zip->locateName($zipPath) !== false) { - $this->path = $path; - $this->setSizesAndType($path); - } - } else { - throw new PhpSpreadsheetException("File $path not found!"); } - } else { - $this->path = $path; + } + if ($this->path === '' && $verifyFile) { + throw new PhpSpreadsheetException("File $path not found!"); + } + + if ($this->worksheet !== null) { + if ($this->path !== '') { + $this->worksheet->getCell($this->coordinates); + } } return $this; } + private function isImage(string $path): bool + { + $mime = (string) @mime_content_type($path); + $retVal = false; + if (strpos($mime, 'image/') === 0) { + $retVal = true; + } elseif ($mime === 'application/octet-stream') { + $extension = pathinfo($path, PATHINFO_EXTENSION); + $retVal = in_array($extension, ['bin', 'emf'], true); + } + + return $retVal; + } + /** * Get isURL. */ @@ -152,6 +216,8 @@ public function getIsURL(): bool * Set isURL. * * @return $this + * + * @deprecated 3.7.0 not needed, property is set by setPath */ public function setIsURL(bool $isUrl): self { diff --git a/src/PhpSpreadsheet/Worksheet/Table.php b/src/PhpSpreadsheet/Worksheet/Table.php index dc2a4f8a52..bab83d3420 100644 --- a/src/PhpSpreadsheet/Worksheet/Table.php +++ b/src/PhpSpreadsheet/Worksheet/Table.php @@ -123,10 +123,10 @@ public function setName(string $name): self ) { throw new PhpSpreadsheetException('The table name can\'t be the same as a cell reference'); } - if (!preg_match('/^[\p{L}_\\\\]/iu', $name)) { + if (!preg_match('/^[\p{L}_\\\]/iu', $name)) { throw new PhpSpreadsheetException('The table name must begin a name with a letter, an underscore character (_), or a backslash (\)'); } - if (!preg_match('/^[\p{L}_\\\\][\p{L}\p{M}0-9\._]+$/iu', $name)) { + if (!preg_match('/^[\p{L}_\\\][\p{L}\p{M}0-9\._]+$/iu', $name)) { throw new PhpSpreadsheetException('The table name contains invalid characters'); } @@ -180,7 +180,7 @@ private function updateStructuredReferences(string $name): void private function updateStructuredReferencesInCells(Worksheet $worksheet, string $newName): void { - $pattern = '/' . preg_quote($this->name) . '\[/mui'; + $pattern = '/' . preg_quote($this->name, '/') . '\[/mui'; foreach ($worksheet->getCoordinates(false) as $coordinate) { $cell = $worksheet->getCell($coordinate); @@ -196,7 +196,7 @@ private function updateStructuredReferencesInCells(Worksheet $worksheet, string private function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $newName): void { - $pattern = '/' . preg_quote($this->name) . '\[/mui'; + $pattern = '/' . preg_quote($this->name, '/') . '\[/mui'; foreach ($spreadsheet->getNamedFormulae() as $namedFormula) { $formula = $namedFormula->getValue(); @@ -324,7 +324,7 @@ public function setRangeToMaxRow(): self { if ($this->workSheet !== null) { $thisrange = $this->range; - $range = (string) preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange); + $range = (string) preg_replace('/\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange); if ($range !== $thisrange) { $this->setRange($range); } diff --git a/src/PhpSpreadsheet/Worksheet/Table/Column.php b/src/PhpSpreadsheet/Worksheet/Table/Column.php index 30630c0d46..32dd4c4f83 100644 --- a/src/PhpSpreadsheet/Worksheet/Table/Column.php +++ b/src/PhpSpreadsheet/Worksheet/Table/Column.php @@ -225,7 +225,7 @@ public static function updateStructuredReferences(?Worksheet $workSheet, ?string private static function updateStructuredReferencesInCells(Worksheet $worksheet, string $oldTitle, string $newTitle): void { - $pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui'; + $pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui'; foreach ($worksheet->getCoordinates(false) as $coordinate) { $cell = $worksheet->getCell($coordinate); @@ -241,7 +241,7 @@ private static function updateStructuredReferencesInCells(Worksheet $worksheet, private static function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $oldTitle, string $newTitle): void { - $pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui'; + $pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui'; foreach ($spreadsheet->getNamedFormulae() as $namedFormula) { $formula = $namedFormula->getValue(); diff --git a/src/PhpSpreadsheet/Worksheet/Validations.php b/src/PhpSpreadsheet/Worksheet/Validations.php index aab3aae447..c9a8f28726 100644 --- a/src/PhpSpreadsheet/Worksheet/Validations.php +++ b/src/PhpSpreadsheet/Worksheet/Validations.php @@ -53,6 +53,9 @@ public static function validateCellOrCellRange($cellRange): string return self::validateCellRange($cellRange); } + private const SETMAXROW = '${1}1:${2}' . AddressRange::MAX_ROW; + private const SETMAXCOL = 'A${1}:' . AddressRange::MAX_COLUMN . '${2}'; + /** * Validate a cell range. * @@ -68,8 +71,8 @@ public static function validateCellRange($cellRange): string // Convert Column ranges like 'A:C' to 'A1:C1048576' // or Row ranges like '1:3' to 'A1:XFD3' $addressRange = (string) preg_replace( - ['/^([A-Z]+):([A-Z]+)$/i', '/^(\\d+):(\\d+)$/'], - ['${1}1:${2}1048576', 'A${1}:XFD${2}'], + ['/^([A-Z]+):([A-Z]+)$/i', '/^(\d+):(\d+)$/'], + [self::SETMAXROW, self::SETMAXCOL], $addressRange ); diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 06f86cd173..0a875d61df 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; use ArrayObject; +use Composer\Pcre\Preg; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Cell\AddressRange; @@ -20,7 +21,6 @@ use PhpOffice\PhpSpreadsheet\Comment; use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Exception; -use PhpOffice\PhpSpreadsheet\IComparable; use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared; @@ -31,7 +31,7 @@ use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Style; -class Worksheet implements IComparable +class Worksheet { // Break types public const BREAK_NONE = 0; @@ -338,17 +338,10 @@ class Worksheet implements IComparable */ private $tabColor; - /** - * Dirty flag. - * - * @var bool - */ - private $dirty = true; - /** * Hash. * - * @var string + * @var int */ private $hash; @@ -368,6 +361,7 @@ public function __construct(?Spreadsheet $parent = null, $title = 'Worksheet') { // Set parent and title $this->parent = $parent; + $this->hash = spl_object_id($this); $this->setTitle($title, false); // setTitle can change $pTitle $this->setCodeName($this->getTitle()); @@ -424,6 +418,11 @@ public function __destruct() $this->rowDimensions = []; } + public function __wakeup(): void + { + $this->hash = spl_object_id($this); + } + /** * Return the cell collection. * @@ -543,9 +542,18 @@ public function getDefaultRowDimension() */ public function getColumnDimensions() { + /** @var callable */ + $callable = [self::class, 'columnDimensionCompare']; + uasort($this->columnDimensions, $callable); + return $this->columnDimensions; } + private static function columnDimensionCompare(ColumnDimension $a, ColumnDimension $b): int + { + return $a->getColumnNumeric() - $b->getColumnNumeric(); + } + /** * Get default column dimension. * @@ -905,7 +913,7 @@ public function setTitle($title, $updateFormulaCellReferences = true, $validate // Syntax check self::checkSheetTitle($title); - if ($this->parent) { + if ($this->parent && $this->parent->getIndex($this, true) >= 0) { // Is there already such sheet name? if ($this->parent->sheetNameExists($title)) { // Use name, but append with lowest possible integer @@ -934,9 +942,8 @@ public function setTitle($title, $updateFormulaCellReferences = true, $validate // Set title $this->title = $title; - $this->dirty = true; - if ($this->parent && $this->parent->getCalculationEngine()) { + if ($this->parent && $this->parent->getIndex($this, true) >= 0 && $this->parent->getCalculationEngine()) { // New title $newTitle = $this->getTitle(); $this->parent->getCalculationEngine() @@ -1079,7 +1086,6 @@ public function getProtection() public function setProtection(Protection $protection) { $this->protection = $protection; - $this->dirty = true; return $this; } @@ -1302,8 +1308,8 @@ private function getWorksheetAndCoordinate(string $coordinate): array throw new Exception('Sheet not found for name: ' . $worksheetReference[0]); } } elseif ( - !preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $coordinate) && - preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/iu', $coordinate) + !Preg::isMatch('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $coordinate) && + Preg::isMatch('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/iu', $coordinate) ) { // Named range? $namedRange = $this->validateNamedRange($coordinate, true); @@ -1888,7 +1894,7 @@ public function mergeCells($range, $behaviour = self::MERGE_CELL_CONTENT_EMPTY) $range .= ":{$range}"; } - if (preg_match('/^([A-Z]+)(\\d+):([A-Z]+)(\\d+)$/', $range, $matches) !== 1) { + if (!Preg::isMatch('/^([A-Z]+)(\d+):([A-Z]+)(\d+)$/', $range, $matches)) { throw new Exception('Merge must be on a valid range of cells.'); } @@ -2545,6 +2551,42 @@ public function removeRow(int $row, int $numberOfRows = 1) if ($row < 1) { throw new Exception('Rows to be deleted should at least start from row 1.'); } + $startRow = $row; + $endRow = $startRow + $numberOfRows - 1; + $removeKeys = []; + $addKeys = []; + foreach ($this->mergeCells as $key => $value) { + if ( + Preg::isMatch( + '/^([a-z]{1,3})(\d+):([a-z]{1,3})(\d+)/i', + $key, + $matches + ) + ) { + $startMergeInt = (int) $matches[2]; + $endMergeInt = (int) $matches[4]; + if ($startMergeInt >= $startRow) { + if ($startMergeInt <= $endRow) { + $removeKeys[] = $key; + } + } elseif ($endMergeInt >= $startRow) { + if ($endMergeInt <= $endRow) { + $temp = $endMergeInt - 1; + $removeKeys[] = $key; + if ($temp !== $startMergeInt) { + $temp3 = $matches[1] . $matches[2] . ':' . $matches[3] . $temp; + $addKeys[] = $temp3; + } + } + } + } + } + foreach ($removeKeys as $key) { + unset($this->mergeCells[$key]); + } + foreach ($addKeys as $key) { + $this->mergeCells[$key] = $key; + } $holdRowDimensions = $this->removeRowDimensions($row, $numberOfRows); $highestRow = $this->getHighestDataRow(); @@ -2601,6 +2643,43 @@ public function removeColumn(string $column, int $numberOfColumns = 1) if (is_numeric($column)) { throw new Exception('Column references should not be numeric.'); } + $startColumnInt = Coordinate::columnIndexFromString($column); + $endColumnInt = $startColumnInt + $numberOfColumns - 1; + $removeKeys = []; + $addKeys = []; + foreach ($this->mergeCells as $key => $value) { + if ( + Preg::isMatch( + '/^([a-z]{1,3})(\d+):([a-z]{1,3})(\d+)/i', + $key, + $matches + ) + ) { + $startMergeInt = Coordinate::columnIndexFromString($matches[1]); + $endMergeInt = Coordinate::columnIndexFromString($matches[3]); + if ($startMergeInt >= $startColumnInt) { + if ($startMergeInt <= $endColumnInt) { + $removeKeys[] = $key; + } + } elseif ($endMergeInt >= $startColumnInt) { + if ($endMergeInt <= $endColumnInt) { + $temp = Coordinate::columnIndexFromString($matches[3]) - 1; + $temp2 = Coordinate::stringFromColumnIndex($temp); + $removeKeys[] = $key; + if ($temp2 !== $matches[1]) { + $temp3 = $matches[1] . $matches[2] . ':' . $temp2 . $matches[4]; + $addKeys[] = $temp3; + } + } + } + } + } + foreach ($removeKeys as $key) { + unset($this->mergeCells[$key]); + } + foreach ($addKeys as $key) { + $this->mergeCells[$key] = $key; + } $highestColumn = $this->getHighestDataColumn(); $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn); @@ -3128,7 +3207,7 @@ private function validateNamedRange(string $definedName, bool $returnNullIfInval if ($namedRange->getLocalOnly()) { $worksheet = $namedRange->getWorksheet(); - if ($worksheet === null || $this->getHashCode() !== $worksheet->getHashCode()) { + if ($worksheet === null || $this->getHashInt() !== $worksheet->getHashInt()) { if ($returnNullIfInvalid) { return null; } @@ -3269,17 +3348,20 @@ public function garbageCollect() } /** - * Get hash code. + * @deprecated 3.5.0 use getHashInt instead. * * @return string Hash code */ public function getHashCode() { - if ($this->dirty) { - $this->hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' : 'f') . __CLASS__); - $this->dirty = false; - } + return (string) $this->hash; + } + /** + * @return int Hash code + */ + public function getHashInt() + { return $this->hash; } @@ -3611,6 +3693,7 @@ public function __clone() } } } + $this->hash = spl_object_id($this); } /** @@ -3693,6 +3776,6 @@ public function hasCodeName() public static function nameRequiresQuotes(string $sheetName): bool { - return preg_match(self::SHEET_NAME_REQUIRES_NO_QUOTES, $sheetName) !== 1; + return !Preg::isMatch(self::SHEET_NAME_REQUIRES_NO_QUOTES, $sheetName); } } diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index 575197aecb..5da3a1fd49 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -7,9 +7,11 @@ use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Chart\Chart; +use PhpOffice\PhpSpreadsheet\Document\Properties; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\Run; use PhpOffice\PhpSpreadsheet\Settings; +use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\Font as SharedFont; @@ -342,13 +344,21 @@ public function writeAllSheets() private static function generateMeta(?string $val, string $desc): string { - return $val + return ($val || $val === '0') ? (' ' . PHP_EOL) : ''; } public const BODY_LINE = ' ' . PHP_EOL; + private const CUSTOM_TO_META = [ + Properties::PROPERTY_TYPE_BOOLEAN => 'bool', + Properties::PROPERTY_TYPE_DATE => 'date', + Properties::PROPERTY_TYPE_FLOAT => 'float', + Properties::PROPERTY_TYPE_INTEGER => 'int', + Properties::PROPERTY_TYPE_STRING => 'string', + ]; + /** * Generate HTML header. * @@ -374,6 +384,36 @@ public function generateHTMLHeader($includeStyles = false) $html .= self::generateMeta($properties->getCategory(), 'category'); $html .= self::generateMeta($properties->getCompany(), 'company'); $html .= self::generateMeta($properties->getManager(), 'manager'); + $html .= self::generateMeta($properties->getLastModifiedBy(), 'lastModifiedBy'); + $date = Date::dateTimeFromTimestamp((string) $properties->getCreated()); + $date->setTimeZone(Date::getDefaultOrLocalTimeZone()); + $html .= self::generateMeta($date->format(DATE_W3C), 'created'); + $date = Date::dateTimeFromTimestamp((string) $properties->getModified()); + $date->setTimeZone(Date::getDefaultOrLocalTimeZone()); + $html .= self::generateMeta($date->format(DATE_W3C), 'modified'); + + $customProperties = $properties->getCustomProperties(); + foreach ($customProperties as $customProperty) { + $propertyValue = $properties->getCustomPropertyValue($customProperty); + $propertyType = $properties->getCustomPropertyType($customProperty); + $propertyQualifier = self::CUSTOM_TO_META[$propertyType] ?? null; + if ($propertyQualifier !== null) { + if ($propertyType === Properties::PROPERTY_TYPE_BOOLEAN) { + $propertyValue = $propertyValue ? '1' : '0'; + } elseif ($propertyType === Properties::PROPERTY_TYPE_DATE) { + $date = Date::dateTimeFromTimestamp((string) $propertyValue); + $date->setTimeZone(Date::getDefaultOrLocalTimeZone()); + $propertyValue = $date->format(DATE_W3C); + } else { + $propertyValue = (string) $propertyValue; + } + $html .= self::generateMeta($propertyValue, htmlspecialchars("custom.$propertyQualifier.$customProperty")); + } + } + + if (!empty($properties->getHyperlinkBase())) { + $html .= ' ' . PHP_EOL; + } $html .= $includeStyles ? $this->generateStyles(true) : $this->generatePageDeclarations(true); @@ -523,7 +563,7 @@ public function generateNavigation() $html .= '
    jav ascript:alert()j avascript:alert(1)Click mejavascript:alert(\'hello1\')javascript:alert(\'hello2\')
    + + + decimal numbers below: + a + + + bb + + + ccc + + + dddd + + + eeeee + + + ffffff + +
    + + + + + + 3 + 1 + 2 + + + False + False + + + C1 + Whole + 1 + 9 + Enter a digit + Please enter a single digit from 1 to 9 + Sorry, only digits from 1 to 9 are allowed + Wrong number + + + R2C2:R1048576C2 + Decimal + Greater + 10 + Decimal number bigger than 10 + + + R1C2 + Greater + + Decimal number bigger than 10 + + + R1C3 + Date + GreaterOrEqual + 44927 + date from 2023-01-01 + + + R2C3 + Time + 0.333333333333333 + 0.583333333333333 + time from 8:00 to 14:00 + + + R3C3 + TextLength + Less + 8 + text < 8 characters + + + R4C3 + List + + + "a,b,c,d,e" + letters a to e + + + R1C4 + Whole + 1+1 + 2+2 + integer from 2 to 4 + + + R2C4 + List + R[-1]C[1]:R[3]C[1] + any of the texts in E1:E5 + + + R7C6 + Whole + GreaterOrEqual + -6 + Integer greater than or equal to -6 + + + R6C6 + Whole + LessOrEqual + 12 + Integer less than or equal to 12 + + + R5C6 + Whole + Less + 8 + Integer less than 8 + + + R4C6 + Whole + Equal + -3 + Negative 3 is only valid input + + + R3C6 + Whole + NotEqual + 7 + Any integer except 7 + + + R2C6 + Whole + NotBetween + -5 + 5 + Integer not between -5 and 5 + + + R1C6 + Whole + 2 + 5 + Integer between 2 and 5 + + + R8C6 + Whole + Greater + 5 + Integer greater than 5 + + + R1C7 + Decimal + 2 + 5 + Float between 2 and 5 + + + diff --git a/tests/data/Reader/Xml/hyperlinkbase.xml b/tests/data/Reader/Xml/hyperlinkbase.xml new file mode 100644 index 0000000000..e3448ee994 --- /dev/null +++ b/tests/data/Reader/Xml/hyperlinkbase.xml @@ -0,0 +1,91 @@ + + + + + title + topic + author + keyword1, keyword2 + no comment + last author + 2023-05-18T11:21:43Z + 2023-05-18T11:30:00Z + category + manager + company + https://phpspreadsheet.readthedocs.io/en/latest/ + 16.00 + + + TheString + 12345 + 2023-05-18T10:00:00Z + 2023-05-19T11:00:00Z + 1 + 0 + 1.2345 + + + + + + 6820 + 19200 + 32767 + 32767 + False + False + + + + + + + + + references/features-cross-reference/ + + + topics/accessing-cells/ + + + https://www.google.com + + + https://www.yahoo.com + +
    + + + + + + 3 + 3 + + + False + False + +
    +
    diff --git a/tests/data/Reader/Xml/sec-w24f.dontuse b/tests/data/Reader/Xml/sec-w24f.dontuse new file mode 100644 index 0000000000..f39faa4fc8 --- /dev/null +++ b/tests/data/Reader/Xml/sec-w24f.dontuse @@ -0,0 +1,65 @@ + + + + + author + author + 2015-06-05T18:19:34Z + 2024-12-25T10:16:07Z + 16.00 + + + + + + 11020 + 19420 + 32767 + 32767 + False + False + + + + + + + + + + + +
    + + +
    +