Browse Source

升级包,安全bug,composer require phpoffice/phpspreadsheet:^1.29.1

lizhen_gitee 4 months ago
parent
commit
5a12c12e20
85 changed files with 14366 additions and 0 deletions
  1. 3275 0
      composer.lock
  2. 6 0
      vendor/ezyang/htmlpurifier/CHANGELOG.md
  3. 223 0
      vendor/phpoffice/phpspreadsheet/.php-cs-fixer.dist.php
  4. 86 0
      vendor/phpoffice/phpspreadsheet/phpstan-conditional.php
  5. 15 0
      vendor/phpoffice/phpspreadsheet/phpunit10.xml.dist
  6. 133 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php
  7. 181 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php
  8. 209 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php
  9. 175 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php
  10. 223 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/BranchPruner.php
  11. 147 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php
  12. 10 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php
  13. 344 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php
  14. 71 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php
  15. 171 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ExcelError.php
  16. 328 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/Value.php
  17. 81 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php
  18. 342 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Sort.php
  19. 141 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Unique.php
  20. 24 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressRange.php
  21. 166 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/CellAddress.php
  22. 136 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/CellRange.php
  23. 125 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/ColumnRange.php
  24. 66 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php
  25. 93 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/RowRange.php
  26. 131 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php
  27. 56 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php
  28. 177 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/ChartColor.php
  29. 873 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php
  30. 36 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php
  31. 226 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php
  32. 126 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php
  33. 109 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php
  34. 89 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php
  35. 46 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php
  36. 139 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php
  37. 27 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseLoader.php
  38. 97 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php
  39. 49 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php
  40. 72 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php
  41. 26 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php
  42. 113 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php
  43. 153 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php
  44. 177 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php
  45. 313 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php
  46. 45 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php
  47. 118 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php
  48. 95 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php
  49. 99 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php
  50. 200 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php
  51. 111 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php
  52. 78 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php
  53. 95 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php
  54. 75 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php
  55. 164 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php
  56. 199 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php
  57. 25 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php
  58. 102 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php
  59. 112 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Currency.php
  60. 125 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php
  61. 50 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php
  62. 44 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php
  63. 153 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php
  64. 37 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.php
  65. 57 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Number.php
  66. 80 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.php
  67. 40 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Percentage.php
  68. 33 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Scientific.php
  69. 105 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php
  70. 8 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Wizard.php
  71. 175 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php
  72. 269 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Theme.php
  73. 51 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFit.php
  74. 58 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageBreak.php
  75. 585 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php
  76. 254 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php
  77. 230 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/TableStyle.php
  78. 118 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php
  79. 76 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/ConditionalHelper.php
  80. 125 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php
  81. 194 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php
  82. 115 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Table.php
  83. 17 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream0.php
  84. 21 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream2.php
  85. 22 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream3.php

+ 3275 - 0
composer.lock

@@ -0,0 +1,3275 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "286a5f3779d1d926765fa1b704caade1",
+    "packages": [
+        {
+            "name": "easywechat-composer/easywechat-composer",
+            "version": "1.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mingyoung/easywechat-composer.git",
+                "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mingyoung/easywechat-composer/zipball/3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd",
+                "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "composer-plugin-api": "^1.0 || ^2.0",
+                "php": ">=7.0"
+            },
+            "require-dev": {
+                "composer/composer": "^1.0 || ^2.0",
+                "phpunit/phpunit": "^6.5 || ^7.0"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "EasyWeChatComposer\\Plugin"
+            },
+            "autoload": {
+                "psr-4": {
+                    "EasyWeChatComposer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "张铭阳",
+                    "email": "mingyoungcheung@gmail.com"
+                }
+            ],
+            "description": "The composer plugin for EasyWeChat",
+            "support": {
+                "issues": "https://github.com/mingyoung/easywechat-composer/issues",
+                "source": "https://github.com/mingyoung/easywechat-composer/tree/1.4.1"
+            },
+            "time": "2021-07-05T04:03:22+00:00"
+        },
+        {
+            "name": "ezyang/htmlpurifier",
+            "version": "v4.16.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ezyang/htmlpurifier.git",
+                "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8",
+                "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "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"
+            },
+            "require-dev": {
+                "cerdic/css-tidy": "^1.7 || ^2.0",
+                "simpletest/simpletest": "dev-master"
+            },
+            "suggest": {
+                "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
+                "ext-bcmath": "Used for unit conversion and imagecrash protection",
+                "ext-iconv": "Converts text to and from non-UTF-8 encodings",
+                "ext-tidy": "Used for pretty-printing HTML"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "library/HTMLPurifier.composer.php"
+                ],
+                "psr-0": {
+                    "HTMLPurifier": "library/"
+                },
+                "exclude-from-classmap": [
+                    "/library/HTMLPurifier/Language/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Edward Z. Yang",
+                    "email": "admin@htmlpurifier.org",
+                    "homepage": "http://ezyang.com"
+                }
+            ],
+            "description": "Standards compliant HTML filter written in PHP",
+            "homepage": "http://htmlpurifier.org/",
+            "keywords": [
+                "html"
+            ],
+            "support": {
+                "issues": "https://github.com/ezyang/htmlpurifier/issues",
+                "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0"
+            },
+            "time": "2022-09-18T07:06:19+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "7.9.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "d281ed313b989f213357e3be1a179f02196ac99b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
+                "reference": "d281ed313b989f213357e3be1a179f02196ac99b",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
+                "guzzlehttp/psr7": "^2.7.0",
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-client": "^1.0",
+                "symfony/deprecation-contracts": "^2.2 || ^3.0"
+            },
+            "provide": {
+                "psr/http-client-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "ext-curl": "*",
+                "guzzle/client-integration-tests": "3.0.2",
+                "php-http/message-factory": "^1.1",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20",
+                "psr/log": "^1.1 || ^2.0 || ^3.0"
+            },
+            "suggest": {
+                "ext-curl": "Required for CURL handler support",
+                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "psr-18",
+                "psr-7",
+                "rest",
+                "web service"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/guzzle/issues",
+                "source": "https://github.com/guzzle/guzzle/tree/7.9.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-24T11:22:20+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "2.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
+                "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/promises/issues",
+                "source": "https://github.com/guzzle/promises/tree/2.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-18T10:29:17+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "2.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+                "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.1 || ^2.0",
+                "ralouphie/getallheaders": "^3.0"
+            },
+            "provide": {
+                "psr/http-factory-implementation": "1.0",
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "http-interop/http-factory-tests": "0.9.0",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+            },
+            "suggest": {
+                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://sagikazarmark.hu"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/psr7/issues",
+                "source": "https://github.com/guzzle/psr7/tree/2.7.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-18T11:15:46+00:00"
+        },
+        {
+            "name": "karsonzhang/fastadmin-addons",
+            "version": "1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/karsonzhang/fastadmin-addons.git",
+                "reference": "12b0b146bbdcb12c9f50c96baa3b7cc5f4c48ad6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/karsonzhang/fastadmin-addons/zipball/12b0b146bbdcb12c9f50c96baa3b7cc5f4c48ad6",
+                "reference": "12b0b146bbdcb12c9f50c96baa3b7cc5f4c48ad6",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "nelexa/zip": "^3.3 || ^4.0",
+                "php": ">=7.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "think-config": {
+                    "addons": "src/config.php"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/common.php"
+                ],
+                "psr-4": {
+                    "think\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Karson",
+                    "email": "karson@fastadmin.net"
+                },
+                {
+                    "name": "xiaobo.sun",
+                    "email": "xiaobo.sun@qq.com"
+                }
+            ],
+            "description": "addons package for fastadmin",
+            "homepage": "https://github.com/karsonzhang/fastadmin-addons",
+            "support": {
+                "issues": "https://github.com/karsonzhang/fastadmin-addons/issues",
+                "source": "https://github.com/karsonzhang/fastadmin-addons/tree/v1.4.0"
+            },
+            "time": "2024-03-28T04:15:16+00:00"
+        },
+        {
+            "name": "maennchen/zipstream-php",
+            "version": "2.2.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/maennchen/ZipStream-PHP.git",
+                "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f",
+                "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "myclabs/php-enum": "^1.5",
+                "php": "^7.4 || ^8.0",
+                "psr/http-message": "^1.0",
+                "symfony/polyfill-mbstring": "^1.0"
+            },
+            "require-dev": {
+                "ext-zip": "*",
+                "friendsofphp/php-cs-fixer": "^3.9",
+                "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0",
+                "mikey179/vfsstream": "^1.6",
+                "php-coveralls/php-coveralls": "^2.4",
+                "phpunit/phpunit": "^8.5.8 || ^9.4.2",
+                "vimeo/psalm": "^4.1"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "ZipStream\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paul Duncan",
+                    "email": "pabs@pablotron.org"
+                },
+                {
+                    "name": "Jonatan Männchen",
+                    "email": "jonatan@maennchen.ch"
+                },
+                {
+                    "name": "Jesse Donat",
+                    "email": "donatj@gmail.com"
+                },
+                {
+                    "name": "András Kolesár",
+                    "email": "kolesar@kolesar.hu"
+                }
+            ],
+            "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
+            "keywords": [
+                "stream",
+                "zip"
+            ],
+            "support": {
+                "issues": "https://github.com/maennchen/ZipStream-PHP/issues",
+                "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.2.6"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/maennchen",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/zipstream",
+                    "type": "open_collective"
+                }
+            ],
+            "time": "2022-11-25T18:57:19+00:00"
+        },
+        {
+            "name": "markbaker/complex",
+            "version": "3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MarkBaker/PHPComplex.git",
+                "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
+                "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
+                "phpcompatibility/php-compatibility": "^9.3",
+                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
+                "squizlabs/php_codesniffer": "^3.7"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Complex\\": "classes/src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mark Baker",
+                    "email": "mark@lange.demon.co.uk"
+                }
+            ],
+            "description": "PHP Class for working with complex numbers",
+            "homepage": "https://github.com/MarkBaker/PHPComplex",
+            "keywords": [
+                "complex",
+                "mathematics"
+            ],
+            "support": {
+                "issues": "https://github.com/MarkBaker/PHPComplex/issues",
+                "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
+            },
+            "time": "2022-12-06T16:21:08+00:00"
+        },
+        {
+            "name": "markbaker/matrix",
+            "version": "3.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MarkBaker/PHPMatrix.git",
+                "reference": "728434227fe21be27ff6d86621a1b13107a2562c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
+                "reference": "728434227fe21be27ff6d86621a1b13107a2562c",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
+                "phpcompatibility/php-compatibility": "^9.3",
+                "phpdocumentor/phpdocumentor": "2.*",
+                "phploc/phploc": "^4.0",
+                "phpmd/phpmd": "2.*",
+                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
+                "sebastian/phpcpd": "^4.0",
+                "squizlabs/php_codesniffer": "^3.7"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Matrix\\": "classes/src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mark Baker",
+                    "email": "mark@demon-angel.eu"
+                }
+            ],
+            "description": "PHP Class for working with matrices",
+            "homepage": "https://github.com/MarkBaker/PHPMatrix",
+            "keywords": [
+                "mathematics",
+                "matrix",
+                "vector"
+            ],
+            "support": {
+                "issues": "https://github.com/MarkBaker/PHPMatrix/issues",
+                "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
+            },
+            "time": "2022-12-02T22:17:43+00:00"
+        },
+        {
+            "name": "monolog/monolog",
+            "version": "2.9.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/monolog.git",
+                "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/a30bfe2e142720dfa990d0a7e573997f5d884215",
+                "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2",
+                "psr/log": "^1.0.1 || ^2.0 || ^3.0"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
+            },
+            "require-dev": {
+                "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+                "doctrine/couchdb": "~1.0@dev",
+                "elasticsearch/elasticsearch": "^7 || ^8",
+                "ext-json": "*",
+                "graylog2/gelf-php": "^1.4.2 || ^2@dev",
+                "guzzlehttp/guzzle": "^7.4",
+                "guzzlehttp/psr7": "^2.2",
+                "mongodb/mongodb": "^1.8",
+                "php-amqplib/php-amqplib": "~2.4 || ^3",
+                "phpspec/prophecy": "^1.15",
+                "phpstan/phpstan": "^1.10",
+                "phpunit/phpunit": "^8.5.38 || ^9.6.19",
+                "predis/predis": "^1.1 || ^2.0",
+                "rollbar/rollbar": "^1.3 || ^2 || ^3",
+                "ruflin/elastica": "^7",
+                "swiftmailer/swiftmailer": "^5.3|^6.0",
+                "symfony/mailer": "^5.4 || ^6",
+                "symfony/mime": "^5.4 || ^6"
+            },
+            "suggest": {
+                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+                "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
+                "ext-mbstring": "Allow to work properly with unicode symbols",
+                "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+                "ext-openssl": "Required to send log messages using SSL",
+                "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
+                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+                "rollbar/rollbar": "Allow sending log messages to Rollbar",
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Monolog\\": "src/Monolog"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "https://seld.be"
+                }
+            ],
+            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+            "homepage": "https://github.com/Seldaek/monolog",
+            "keywords": [
+                "log",
+                "logging",
+                "psr-3"
+            ],
+            "support": {
+                "issues": "https://github.com/Seldaek/monolog/issues",
+                "source": "https://github.com/Seldaek/monolog/tree/2.9.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/Seldaek",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-04-12T20:52:51+00:00"
+        },
+        {
+            "name": "myclabs/php-enum",
+            "version": "1.8.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/php-enum.git",
+                "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/php-enum/zipball/a867478eae49c9f59ece437ae7f9506bfaa27483",
+                "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-json": "*",
+                "php": "^7.3 || ^8.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.5",
+                "squizlabs/php_codesniffer": "1.*",
+                "vimeo/psalm": "^4.6.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "MyCLabs\\Enum\\": "src/"
+                },
+                "classmap": [
+                    "stubs/Stringable.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP Enum contributors",
+                    "homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
+                }
+            ],
+            "description": "PHP Enum implementation",
+            "homepage": "http://github.com/myclabs/php-enum",
+            "keywords": [
+                "enum"
+            ],
+            "support": {
+                "issues": "https://github.com/myclabs/php-enum/issues",
+                "source": "https://github.com/myclabs/php-enum/tree/1.8.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/mnapoli",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-08-04T09:53:51+00:00"
+        },
+        {
+            "name": "nelexa/zip",
+            "version": "4.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Ne-Lexa/php-zip.git",
+                "reference": "88a1b6549be813278ff2dd3b6b2ac188827634a7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Ne-Lexa/php-zip/zipball/88a1b6549be813278ff2dd3b6b2ac188827634a7",
+                "reference": "88a1b6549be813278ff2dd3b6b2ac188827634a7",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-zlib": "*",
+                "php": "^7.4 || ^8.0",
+                "psr/http-message": "*",
+                "symfony/finder": "*"
+            },
+            "require-dev": {
+                "ext-bz2": "*",
+                "ext-dom": "*",
+                "ext-fileinfo": "*",
+                "ext-iconv": "*",
+                "ext-openssl": "*",
+                "ext-xml": "*",
+                "friendsofphp/php-cs-fixer": "^3.4.0",
+                "guzzlehttp/psr7": "^1.6",
+                "phpunit/phpunit": "^9",
+                "symfony/http-foundation": "*",
+                "symfony/var-dumper": "*",
+                "vimeo/psalm": "^4.6"
+            },
+            "suggest": {
+                "ext-bz2": "Needed to support BZIP2 compression",
+                "ext-fileinfo": "Needed to get mime-type file",
+                "ext-iconv": "Needed to support convert zip entry name to requested character encoding",
+                "ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpZip\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ne-Lexa",
+                    "email": "alexey@nelexa.ru",
+                    "role": "Developer"
+                }
+            ],
+            "description": "PhpZip is a php-library for extended work with ZIP-archives. Open, create, update, delete, extract and get info tool. Supports appending to existing ZIP files, WinZip AES encryption, Traditional PKWARE Encryption, BZIP2 compression, external file attributes and ZIP64 extensions. Alternative ZipArchive. It does not require php-zip extension.",
+            "homepage": "https://github.com/Ne-Lexa/php-zip",
+            "keywords": [
+                "archive",
+                "extract",
+                "unzip",
+                "winzip",
+                "zip",
+                "ziparchive"
+            ],
+            "support": {
+                "issues": "https://github.com/Ne-Lexa/php-zip/issues",
+                "source": "https://github.com/Ne-Lexa/php-zip/tree/4.0.2"
+            },
+            "time": "2022-06-17T11:17:46+00:00"
+        },
+        {
+            "name": "overtrue/pinyin",
+            "version": "3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/pinyin.git",
+                "reference": "3b781d267197b74752daa32814d3a2cf5d140779"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/pinyin/zipball/3b781d267197b74752daa32814d3a2cf5d140779",
+                "reference": "3b781d267197b74752daa32814d3a2cf5d140779",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.8"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Overtrue\\Pinyin\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Carlos",
+                    "homepage": "http://github.com/overtrue"
+                }
+            ],
+            "description": "Chinese to pinyin translator.",
+            "homepage": "https://github.com/overtrue/pinyin",
+            "keywords": [
+                "Chinese",
+                "Pinyin",
+                "cn2pinyin"
+            ],
+            "support": {
+                "issues": "https://github.com/overtrue/pinyin/issues",
+                "source": "https://github.com/overtrue/pinyin/tree/master"
+            },
+            "time": "2017-07-10T07:20:01+00:00"
+        },
+        {
+            "name": "overtrue/socialite",
+            "version": "2.0.24",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/socialite.git",
+                "reference": "ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/socialite/zipball/ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec",
+                "reference": "ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/guzzle": "^5.0|^6.0|^7.0",
+                "php": ">=5.6",
+                "symfony/http-foundation": "^2.7|^3.0|^4.0|^5.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "~1.2",
+                "phpunit/phpunit": "^6.0|^7.0|^8.0|^9.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Overtrue\\Socialite\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "overtrue",
+                    "email": "anzhengchao@gmail.com"
+                }
+            ],
+            "description": "A collection of OAuth 2 packages that extracts from laravel/socialite.",
+            "keywords": [
+                "login",
+                "oauth",
+                "qq",
+                "social",
+                "wechat",
+                "weibo"
+            ],
+            "support": {
+                "issues": "https://github.com/overtrue/socialite/issues",
+                "source": "https://github.com/overtrue/socialite/tree/2.0.24"
+            },
+            "funding": [
+                {
+                    "url": "https://www.patreon.com/overtrue",
+                    "type": "patreon"
+                }
+            ],
+            "time": "2021-05-13T16:04:48+00:00"
+        },
+        {
+            "name": "overtrue/wechat",
+            "version": "4.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/w7corp/easywechat.git",
+                "reference": "52af4cbe777cd4aea307beafa0a4518c347467b1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/w7corp/easywechat/zipball/52af4cbe777cd4aea307beafa0a4518c347467b1",
+                "reference": "52af4cbe777cd4aea307beafa0a4518c347467b1",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "easywechat-composer/easywechat-composer": "^1.1",
+                "ext-fileinfo": "*",
+                "ext-openssl": "*",
+                "ext-simplexml": "*",
+                "guzzlehttp/guzzle": "^6.2 || ^7.0",
+                "monolog/monolog": "^1.22 || ^2.0",
+                "overtrue/socialite": "~2.0",
+                "php": ">=7.2",
+                "pimple/pimple": "^3.0",
+                "psr/simple-cache": "^1.0",
+                "symfony/cache": "^3.3 || ^4.3 || ^5.0",
+                "symfony/event-dispatcher": "^4.3 || ^5.0",
+                "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0 || ^5.0",
+                "symfony/psr-http-message-bridge": "^0.3 || ^1.0 || ^2.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^2.15",
+                "mikey179/vfsstream": "^1.6",
+                "mockery/mockery": "^1.2.3",
+                "phpstan/phpstan": "^0.12.0",
+                "phpunit/phpunit": "^7.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/Kernel/Support/Helpers.php",
+                    "src/Kernel/Helpers.php"
+                ],
+                "psr-4": {
+                    "EasyWeChat\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "overtrue",
+                    "email": "anzhengchao@gmail.com"
+                }
+            ],
+            "description": "微信SDK",
+            "keywords": [
+                "easywechat",
+                "sdk",
+                "wechat",
+                "weixin",
+                "weixin-sdk"
+            ],
+            "support": {
+                "issues": "https://github.com/w7corp/easywechat/issues",
+                "source": "https://github.com/w7corp/easywechat/tree/4.6.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/overtrue",
+                    "type": "github"
+                }
+            ],
+            "abandoned": "w7corp/easywechat",
+            "time": "2022-08-24T07:30:42+00:00"
+        },
+        {
+            "name": "phpoffice/phpspreadsheet",
+            "version": "1.29.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
+                "reference": "3a5a818d7d3e4b5bd2e56fb9de44dbded6eae07f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/3a5a818d7d3e4b5bd2e56fb9de44dbded6eae07f",
+                "reference": "3a5a818d7d3e4b5bd2e56fb9de44dbded6eae07f",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-dom": "*",
+                "ext-fileinfo": "*",
+                "ext-gd": "*",
+                "ext-iconv": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-simplexml": "*",
+                "ext-xml": "*",
+                "ext-xmlreader": "*",
+                "ext-xmlwriter": "*",
+                "ext-zip": "*",
+                "ext-zlib": "*",
+                "ezyang/htmlpurifier": "^4.15",
+                "maennchen/zipstream-php": "^2.1 || ^3.0",
+                "markbaker/complex": "^3.0",
+                "markbaker/matrix": "^3.0",
+                "php": "^7.4 || ^8.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",
+                "friendsofphp/php-cs-fixer": "^3.2",
+                "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",
+                "squizlabs/php_codesniffer": "^3.7",
+                "tecnickcom/tcpdf": "^6.5"
+            },
+            "suggest": {
+                "dompdf/dompdf": "Option for rendering PDF with PDF Writer",
+                "ext-intl": "PHP Internationalization Functions",
+                "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
+                "mpdf/mpdf": "Option for rendering PDF with PDF Writer",
+                "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Maarten Balliauw",
+                    "homepage": "https://blog.maartenballiauw.be"
+                },
+                {
+                    "name": "Mark Baker",
+                    "homepage": "https://markbakeruk.net"
+                },
+                {
+                    "name": "Franck Lefevre",
+                    "homepage": "https://rootslabs.net"
+                },
+                {
+                    "name": "Erik Tilt"
+                },
+                {
+                    "name": "Adrien Crivelli"
+                }
+            ],
+            "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
+            "homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
+            "keywords": [
+                "OpenXML",
+                "excel",
+                "gnumeric",
+                "ods",
+                "php",
+                "spreadsheet",
+                "xls",
+                "xlsx"
+            ],
+            "support": {
+                "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
+                "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.2"
+            },
+            "time": "2024-09-29T07:04:47+00:00"
+        },
+        {
+            "name": "pimple/pimple",
+            "version": "v3.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/silexphp/Pimple.git",
+                "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed",
+                "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "psr/container": "^1.1 || ^2.0"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "^5.4@dev"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.4.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Pimple": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Pimple, a simple Dependency Injection Container",
+            "homepage": "https://pimple.symfony.com",
+            "keywords": [
+                "container",
+                "dependency injection"
+            ],
+            "support": {
+                "source": "https://github.com/silexphp/Pimple/tree/v3.5.0"
+            },
+            "time": "2021-10-28T11:13:42+00:00"
+        },
+        {
+            "name": "psr/cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/cache.git",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Cache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for caching libraries",
+            "keywords": [
+                "cache",
+                "psr",
+                "psr-6"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/cache/tree/master"
+            },
+            "time": "2016-08-06T20:24:11+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "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/2.0.2"
+            },
+            "time": "2021-11-05T16:47:00+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": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\EventDispatcher\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Standard interfaces for event handling.",
+            "keywords": [
+                "events",
+                "psr",
+                "psr-14"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/event-dispatcher/issues",
+                "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
+            },
+            "time": "2019-01-08T18:20:26+00:00"
+        },
+        {
+            "name": "psr/http-client",
+            "version": "1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-client.git",
+                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "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"
+            },
+            "time": "2023-09-23T14:17:50+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": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "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",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+                "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-message/tree/1.1"
+            },
+            "time": "2023-04-04T09:50:52+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": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "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": "psr/simple-cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/simple-cache/tree/master"
+            },
+            "time": "2017-10-23T01:57:42+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "support": {
+                "issues": "https://github.com/ralouphie/getallheaders/issues",
+                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+            },
+            "time": "2019-03-08T08:55:37+00:00"
+        },
+        {
+            "name": "symfony/cache",
+            "version": "v5.4.42",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/cache.git",
+                "reference": "6f5f750692bd5a212e01a4f1945fd856bceef89e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/cache/zipball/6f5f750692bd5a212e01a4f1945fd856bceef89e",
+                "reference": "6f5f750692bd5a212e01a4f1945fd856bceef89e",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "psr/cache": "^1.0|^2.0",
+                "psr/log": "^1.1|^2|^3",
+                "symfony/cache-contracts": "^1.1.7|^2",
+                "symfony/deprecation-contracts": "^2.1|^3",
+                "symfony/polyfill-php73": "^1.9",
+                "symfony/polyfill-php80": "^1.16",
+                "symfony/service-contracts": "^1.1|^2|^3",
+                "symfony/var-exporter": "^4.4|^5.0|^6.0"
+            },
+            "conflict": {
+                "doctrine/dbal": "<2.13.1",
+                "symfony/dependency-injection": "<4.4",
+                "symfony/http-kernel": "<4.4",
+                "symfony/var-dumper": "<4.4"
+            },
+            "provide": {
+                "psr/cache-implementation": "1.0|2.0",
+                "psr/simple-cache-implementation": "1.0|2.0",
+                "symfony/cache-implementation": "1.0|2.0"
+            },
+            "require-dev": {
+                "cache/integration-tests": "dev-master",
+                "doctrine/cache": "^1.6|^2.0",
+                "doctrine/dbal": "^2.13.1|^3|^4",
+                "predis/predis": "^1.1|^2.0",
+                "psr/simple-cache": "^1.0|^2.0",
+                "symfony/config": "^4.4|^5.0|^6.0",
+                "symfony/dependency-injection": "^4.4|^5.0|^6.0",
+                "symfony/filesystem": "^4.4|^5.0|^6.0",
+                "symfony/http-kernel": "^4.4|^5.0|^6.0",
+                "symfony/messenger": "^4.4|^5.0|^6.0",
+                "symfony/var-dumper": "^4.4|^5.0|^6.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Cache\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides extended PSR-6, PSR-16 (and tags) implementations",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "caching",
+                "psr6"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/cache/tree/v5.4.42"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-10T06:02:18+00:00"
+        },
+        {
+            "name": "symfony/cache-contracts",
+            "version": "v2.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/cache-contracts.git",
+                "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc",
+                "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "psr/cache": "^1.0|^2.0|^3.0"
+            },
+            "suggest": {
+                "symfony/cache-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.5-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Cache\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to caching",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/cache-contracts/tree/v2.5.2"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-01-02T09:53:40+00:00"
+        },
+        {
+            "name": "symfony/deprecation-contracts",
+            "version": "v2.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/deprecation-contracts.git",
+                "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
+                "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.5-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "function.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "A generic function and convention to trigger deprecation notices",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-01-02T09:53:40+00:00"
+        },
+        {
+            "name": "symfony/event-dispatcher",
+            "version": "v5.4.40",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher.git",
+                "reference": "a54e2a8a114065f31020d6a89ede83e34c3b27a4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a54e2a8a114065f31020d6a89ede83e34c3b27a4",
+                "reference": "a54e2a8a114065f31020d6a89ede83e34c3b27a4",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/deprecation-contracts": "^2.1|^3",
+                "symfony/event-dispatcher-contracts": "^2|^3",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "conflict": {
+                "symfony/dependency-injection": "<4.4"
+            },
+            "provide": {
+                "psr/event-dispatcher-implementation": "1.0",
+                "symfony/event-dispatcher-implementation": "2.0"
+            },
+            "require-dev": {
+                "psr/log": "^1|^2|^3",
+                "symfony/config": "^4.4|^5.0|^6.0",
+                "symfony/dependency-injection": "^4.4|^5.0|^6.0",
+                "symfony/error-handler": "^4.4|^5.0|^6.0",
+                "symfony/expression-language": "^4.4|^5.0|^6.0",
+                "symfony/http-foundation": "^4.4|^5.0|^6.0",
+                "symfony/service-contracts": "^1.1|^2|^3",
+                "symfony/stopwatch": "^4.4|^5.0|^6.0"
+            },
+            "suggest": {
+                "symfony/dependency-injection": "",
+                "symfony/http-kernel": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\EventDispatcher\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.40"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-05-31T14:33:22+00:00"
+        },
+        {
+            "name": "symfony/event-dispatcher-contracts",
+            "version": "v2.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+                "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1",
+                "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "psr/event-dispatcher": "^1"
+            },
+            "suggest": {
+                "symfony/event-dispatcher-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.5-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\EventDispatcher\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to dispatching event",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-01-02T09:53:40+00:00"
+        },
+        {
+            "name": "symfony/finder",
+            "version": "v5.4.42",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "0724c51fa067b198e36506d2864e09a52180998a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/0724c51fa067b198e36506d2864e09a52180998a",
+                "reference": "0724c51fa067b198e36506d2864e09a52180998a",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/deprecation-contracts": "^2.1|^3",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Finds files and directories via an intuitive fluent interface",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/finder/tree/v5.4.42"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-22T08:53:29+00:00"
+        },
+        {
+            "name": "symfony/http-foundation",
+            "version": "v5.4.44",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/http-foundation.git",
+                "reference": "ae0d217e5932aa0b70ddb4cf7822cc76d48aee53"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ae0d217e5932aa0b70ddb4cf7822cc76d48aee53",
+                "reference": "ae0d217e5932aa0b70ddb4cf7822cc76d48aee53",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/deprecation-contracts": "^2.1|^3",
+                "symfony/polyfill-mbstring": "~1.1",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "require-dev": {
+                "predis/predis": "^1.0|^2.0",
+                "symfony/cache": "^4.4|^5.0|^6.0",
+                "symfony/dependency-injection": "^5.4|^6.0",
+                "symfony/expression-language": "^4.4|^5.0|^6.0",
+                "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4",
+                "symfony/mime": "^4.4|^5.0|^6.0",
+                "symfony/rate-limiter": "^5.2|^6.0"
+            },
+            "suggest": {
+                "symfony/mime": "To use the file extension guesser"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\HttpFoundation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Defines an object-oriented layer for the HTTP specification",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/http-foundation/tree/v5.4.44"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-09-15T07:55:06+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.29.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+                "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "provide": {
+                "ext-mbstring": "*"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-01-29T20:11:03+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php73",
+            "version": "v1.31.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php73.git",
+                "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb",
+                "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php73\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-09-09T11:45:10+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.31.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
+                "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-09-09T11:45:10+00:00"
+        },
+        {
+            "name": "symfony/psr-http-message-bridge",
+            "version": "v2.3.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/psr-http-message-bridge.git",
+                "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/581ca6067eb62640de5ff08ee1ba6850a0ee472e",
+                "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "psr/http-message": "^1.0 || ^2.0",
+                "symfony/deprecation-contracts": "^2.5 || ^3.0",
+                "symfony/http-foundation": "^5.4 || ^6.0"
+            },
+            "require-dev": {
+                "nyholm/psr7": "^1.1",
+                "psr/log": "^1.1 || ^2 || ^3",
+                "symfony/browser-kit": "^5.4 || ^6.0",
+                "symfony/config": "^5.4 || ^6.0",
+                "symfony/event-dispatcher": "^5.4 || ^6.0",
+                "symfony/framework-bundle": "^5.4 || ^6.0",
+                "symfony/http-kernel": "^5.4 || ^6.0",
+                "symfony/phpunit-bridge": "^6.2"
+            },
+            "suggest": {
+                "nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
+            },
+            "type": "symfony-bridge",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Bridge\\PsrHttpMessage\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://symfony.com/contributors"
+                }
+            ],
+            "description": "PSR HTTP message bridge",
+            "homepage": "http://symfony.com",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr-17",
+                "psr-7"
+            ],
+            "support": {
+                "issues": "https://github.com/symfony/psr-http-message-bridge/issues",
+                "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-07-26T11:53:26+00:00"
+        },
+        {
+            "name": "symfony/service-contracts",
+            "version": "v1.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/191afdcb5804db960d26d8566b7e9a2843cab3a0",
+                "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.1.3"
+            },
+            "suggest": {
+                "psr/container": "",
+                "symfony/service-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Service\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/service-contracts/tree/v1.1.2"
+            },
+            "time": "2019-05-28T07:50:59+00:00"
+        },
+        {
+            "name": "symfony/var-exporter",
+            "version": "v5.4.40",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/var-exporter.git",
+                "reference": "6a13d37336d512927986e09f19a4bed24178baa6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/6a13d37336d512927986e09f19a4bed24178baa6",
+                "reference": "6a13d37336d512927986e09f19a4bed24178baa6",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "require-dev": {
+                "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\VarExporter\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Allows exporting any serializable PHP data structure to plain PHP code",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "clone",
+                "construct",
+                "export",
+                "hydrate",
+                "instantiate",
+                "serialize"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/var-exporter/tree/v5.4.40"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-05-31T14:33:22+00:00"
+        },
+        {
+            "name": "topthink/framework",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://gitee.com/fastadminnet/framework.git",
+                "reference": "c859e712f50362d8ee3a7cd2e495af5494847bef"
+            },
+            "require": {
+                "php": ">=7.1.0",
+                "topthink/think-installer": "~1.0"
+            },
+            "require-dev": {
+                "johnkary/phpunit-speedtrap": "^1.0",
+                "mikey179/vfsstream": "~1.6",
+                "phpdocumentor/reflection-docblock": "^2.0",
+                "phploc/phploc": "2.*",
+                "phpunit/phpunit": "4.8.*",
+                "sebastian/phpcpd": "2.*"
+            },
+            "default-branch": true,
+            "type": "think-framework",
+            "autoload": {
+                "psr-4": {
+                    "think\\": "library/think"
+                }
+            },
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "the new thinkphp framework",
+            "homepage": "http://thinkphp.cn/",
+            "keywords": [
+                "ORM",
+                "framework",
+                "thinkphp"
+            ],
+            "time": "2024-06-25T09:03:56+00:00"
+        },
+        {
+            "name": "topthink/think-captcha",
+            "version": "v1.0.9",
+            "source": {
+                "type": "git",
+                "url": "https://gitee.com/fastadminnet/think-captcha.git",
+                "reference": "9be9dd7e61c7fa3c478c4b92910d7230b94d0d23"
+            },
+            "require": {
+                "topthink/framework": "~5.0.0 || dev-master",
+                "topthink/think-installer": ">=1.0.10"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\captcha\\": "src/"
+                },
+                "files": [
+                    "src/helper.php"
+                ]
+            },
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "captcha package for thinkphp5",
+            "time": "2023-07-16T09:41:14+00:00"
+        },
+        {
+            "name": "topthink/think-helper",
+            "version": "v1.0.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-helper.git",
+                "reference": "5f92178606c8ce131d36b37a57c58eb71e55f019"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-helper/zipball/5f92178606c8ce131d36b37a57c58eb71e55f019",
+                "reference": "5f92178606c8ce131d36b37a57c58eb71e55f019",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/helper.php"
+                ],
+                "psr-4": {
+                    "think\\helper\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP5 Helper Package",
+            "support": {
+                "issues": "https://github.com/top-think/think-helper/issues",
+                "source": "https://github.com/top-think/think-helper/tree/master"
+            },
+            "time": "2018-10-05T00:43:21+00:00"
+        },
+        {
+            "name": "topthink/think-installer",
+            "version": "v1.0.14",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-installer.git",
+                "reference": "eae1740ac264a55c06134b6685dfb9f837d004d1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-installer/zipball/eae1740ac264a55c06134b6685dfb9f837d004d1",
+                "reference": "eae1740ac264a55c06134b6685dfb9f837d004d1",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "composer-plugin-api": "^1.0||^2.0"
+            },
+            "require-dev": {
+                "composer/composer": "^1.0||^2.0"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "think\\composer\\Plugin"
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\composer\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "support": {
+                "issues": "https://github.com/top-think/think-installer/issues",
+                "source": "https://github.com/top-think/think-installer/tree/v1.0.14"
+            },
+            "time": "2021-03-25T08:34:02+00:00"
+        },
+        {
+            "name": "topthink/think-queue",
+            "version": "v1.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-queue.git",
+                "reference": "250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-queue/zipball/250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245",
+                "reference": "250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "topthink/think-helper": ">=1.0.4",
+                "topthink/think-installer": ">=1.0.10"
+            },
+            "require-dev": {
+                "topthink/framework": "~5.0.0"
+            },
+            "type": "think-extend",
+            "extra": {
+                "think-config": {
+                    "queue": "src/config.php"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/common.php"
+                ],
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP5 Queue Package",
+            "support": {
+                "issues": "https://github.com/top-think/think-queue/issues",
+                "source": "https://github.com/top-think/think-queue/tree/master"
+            },
+            "time": "2018-10-15T10:16:55+00:00"
+        },
+        {
+            "name": "txthinking/mailer",
+            "version": "v2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/txthinking/Mailer.git",
+                "reference": "09013cf9dad3aac195f66ae5309e8c3343c018e9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/txthinking/Mailer/zipball/09013cf9dad3aac195f66ae5309e8c3343c018e9",
+                "reference": "09013cf9dad3aac195f66ae5309e8c3343c018e9",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.2",
+                "psr/log": "~1.0"
+            },
+            "require-dev": {
+                "monolog/monolog": "~1.13",
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Tx\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Cloud",
+                    "email": "cloud@txthinking.com",
+                    "homepage": "http://www.txthinking.com",
+                    "role": "Thinker"
+                },
+                {
+                    "name": "Matt Sowers",
+                    "email": "msowers@erblearn.org"
+                }
+            ],
+            "description": "A very lightweight PHP SMTP mail sender",
+            "homepage": "http://github.com/txthinking/Mailer",
+            "keywords": [
+                "mail",
+                "smtp"
+            ],
+            "support": {
+                "issues": "https://github.com/txthinking/Mailer/issues",
+                "source": "https://github.com/txthinking/Mailer/tree/master"
+            },
+            "time": "2018-10-09T10:47:23+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {
+        "topthink/framework": 20
+    },
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=7.2.0",
+        "ext-json": "*",
+        "ext-curl": "*",
+        "ext-pdo": "*",
+        "ext-bcmath": "*"
+    },
+    "platform-dev": [],
+    "plugin-api-version": "2.1.0"
+}

+ 6 - 0
vendor/ezyang/htmlpurifier/CHANGELOG.md

@@ -0,0 +1,6 @@
+# [4.16.0](https://github.com/ezyang/htmlpurifier/compare/v4.15.0...v4.16.0) (2022-09-18)
+
+
+### Features
+
+* add semantic release ([#307](https://github.com/ezyang/htmlpurifier/issues/307)) ([db31243](https://github.com/ezyang/htmlpurifier/commit/db312435cb9d8d73395f75f9642a43ba6de5e903)), closes [#322](https://github.com/ezyang/htmlpurifier/issues/322) [#323](https://github.com/ezyang/htmlpurifier/issues/323) [#326](https://github.com/ezyang/htmlpurifier/issues/326) [#327](https://github.com/ezyang/htmlpurifier/issues/327) [#328](https://github.com/ezyang/htmlpurifier/issues/328) [#329](https://github.com/ezyang/htmlpurifier/issues/329) [#330](https://github.com/ezyang/htmlpurifier/issues/330) [#331](https://github.com/ezyang/htmlpurifier/issues/331) [#332](https://github.com/ezyang/htmlpurifier/issues/332) [#333](https://github.com/ezyang/htmlpurifier/issues/333) [#337](https://github.com/ezyang/htmlpurifier/issues/337) [#335](https://github.com/ezyang/htmlpurifier/issues/335) [ezyang/htmlpurifier#334](https://github.com/ezyang/htmlpurifier/issues/334) [#336](https://github.com/ezyang/htmlpurifier/issues/336) [#338](https://github.com/ezyang/htmlpurifier/issues/338)

+ 223 - 0
vendor/phpoffice/phpspreadsheet/.php-cs-fixer.dist.php

@@ -0,0 +1,223 @@
+<?php
+
+$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,
+        'array_indentation' => true,
+        'array_syntax' => ['syntax' => 'short'],
+        'backtick_to_shell_exec' => true,
+        'binary_operator_spaces' => true,
+        'blank_line_after_namespace' => true,
+        'blank_line_after_opening_tag' => true,
+        'blank_line_before_statement' => 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,
+        'combine_consecutive_issets' => true,
+        'combine_consecutive_unsets' => true,
+        'combine_nested_dirname' => true,
+        'comment_to_phpdoc' => false, // interferes with annotations
+        'compact_nullable_type_declaration' => true,
+        'concat_space' => ['spacing' => 'one'],
+        'constant_case' => true,
+        'date_time_immutable' => false, // Break our unit tests
+        'declare_equal_normalize' => true,
+        'declare_strict_types' => false, // Too early to adopt strict types
+        'dir_constant' => true,
+        'doctrine_annotation_array_assignment' => true,
+        'doctrine_annotation_braces' => true,
+        'doctrine_annotation_indentation' => true,
+        'doctrine_annotation_spaces' => true,
+        'elseif' => true,
+        'encoding' => true,
+        'ereg_to_preg' => true,
+        'explicit_indirect_variable' => false, // I feel it makes the code actually harder to read
+        'explicit_string_variable' => false, // I feel it makes the code actually harder to read
+        'final_class' => false, // We need non-final classes
+        'final_internal_class' => true,
+        'final_public_method_for_abstract_class' => false, // We need non-final methods
+        'self_static_accessor' => true,
+        'fopen_flag_order' => true,
+        'fopen_flags' => true,
+        'full_opening_tag' => true,
+        'fully_qualified_strict_types' => false,
+        'function_declaration' => true,
+        'function_to_constant' => 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
+        'heredoc_indentation' => true,
+        'heredoc_to_nowdoc' => false, // Not sure about this one
+        'implode_call' => true,
+        'include' => true,
+        'increment_style' => true,
+        'indentation_type' => true,
+        'is_null' => true,
+        'line_ending' => true,
+        'linebreak_after_opening_tag' => true,
+        'list_syntax' => ['syntax' => 'short'],
+        'logical_operators' => true,
+        'lowercase_cast' => true,
+        'lowercase_keywords' => true,
+        'lowercase_static_reference' => true,
+        'magic_constant_casing' => true,
+        'magic_method_casing' => true,
+        'mb_str_functions' => false, // No, too dangerous to change that
+        'method_argument_space' => true,
+        'method_chaining_indentation' => true,
+        'modernize_types_casting' => true,
+        'multiline_comment_opening_closing' => true,
+        'multiline_whitespace_before_semicolons' => true,
+        'native_constant_invocation' => false, // Micro optimization that look messy
+        'native_function_casing' => true,
+        'native_function_invocation' => false, // I suppose this would be best, but I am still unconvinced about the visual aspect of it
+        '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_break_comment' => true,
+        'no_closing_tag' => true,
+        'no_empty_comment' => true,
+        'no_empty_phpdoc' => true,
+        'no_empty_statement' => true,
+        'no_extra_blank_lines' => true,
+        'no_homoglyph_names' => true,
+        'no_leading_import_slash' => true,
+        'no_leading_namespace_whitespace' => true,
+        'no_mixed_echo_print' => true,
+        'no_multiline_whitespace_around_double_arrow' => true,
+        'no_null_property_initialization' => true,
+        'no_php4_constructor' => true,
+        'no_short_bool_cast' => true,
+        'echo_tag_syntax' => ['format' => 'long'],
+        'no_singleline_whitespace_before_semicolons' => true,
+        'no_spaces_after_function_name' => true,
+        'no_spaces_around_offset' => true,
+        'no_superfluous_elseif' => false, // Might be risky on a huge code base
+        //'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_braces' => true,
+        'no_unneeded_final_method' => true,
+        'no_unreachable_default_argument_value' => true,
+        'no_unset_cast' => true,
+        'no_unset_on_property' => true,
+        'no_unused_imports' => true,
+        'no_useless_else' => true,
+        'no_useless_return' => true,
+        'no_whitespace_before_comma_in_array' => true,
+        'no_whitespace_in_blank_line' => true,
+        'non_printable_character' => true,
+        'normalize_index_brace' => true,
+        'not_operator_with_space' => false, // No we prefer to keep '!' without spaces
+        'not_operator_with_successor_space' => false, // idem
+        'nullable_type_declaration_for_default_null_value' => true,
+        'object_operator_without_whitespace' => true,
+        'ordered_class_elements' => false, // We prefer to keep some freedom
+        'ordered_imports' => true,
+        'ordered_interfaces' => true,
+        'php_unit_construct' => true,
+        'php_unit_dedicate_assert' => true,
+        'php_unit_dedicate_assert_internal_type' => true,
+        'php_unit_expectation' => true,
+        'php_unit_fqcn_annotation' => true,
+        'php_unit_internal_class' => false, // Because tests are excluded from package
+        'php_unit_method_casing' => true,
+        'php_unit_mock' => true,
+        'php_unit_mock_short_will_return' => true,
+        'php_unit_namespaced' => true,
+        'php_unit_no_expectation_annotation' => true,
+        'phpdoc_order_by_value' => ['annotations' => ['covers']],
+        'php_unit_set_up_tear_down_visibility' => true,
+        'php_unit_size_class' => false, // That seems extra work to maintain for little benefits
+        'php_unit_strict' => false, // We sometime actually need assertEquals
+        'php_unit_test_annotation' => true,
+        'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
+        'php_unit_test_class_requires_covers' => false, // We don't care as much as we should about coverage
+        'phpdoc_add_missing_param_annotation' => false, // Don't add things that bring no value
+        'phpdoc_align' => false, // Waste of time
+        'phpdoc_annotation_without_dot' => true,
+        'phpdoc_indent' => true,
+        //'phpdoc_inline_tag' => true,
+        'phpdoc_line_span' => false, // Unfortunately our old comments turn even uglier with this
+        'phpdoc_no_access' => true,
+        'phpdoc_no_alias_tag' => true,
+        'phpdoc_no_empty_return' => true,
+        'phpdoc_no_package' => true,
+        'phpdoc_no_useless_inheritdoc' => true,
+        'phpdoc_order' => true,
+        'phpdoc_return_self_reference' => true,
+        'phpdoc_scalar' => true,
+        'phpdoc_separation' => true,
+        'phpdoc_single_line_var_spacing' => true,
+        'phpdoc_summary' => true,
+        'phpdoc_to_comment' => false, // interferes with annotations
+        'phpdoc_to_param_type' => false, // Because experimental, but interesting for one shot use
+        'phpdoc_to_return_type' => false, // idem
+        'phpdoc_trim' => true,
+        'phpdoc_trim_consecutive_blank_line_separation' => true,
+        'phpdoc_types' => true,
+        'phpdoc_types_order' => false,
+        'phpdoc_var_annotation_correct_order' => true,
+        'phpdoc_var_without_name' => true,
+        'pow_to_exponentiation' => true,
+        'protected_to_private' => true,
+        //'psr0' => true,
+        //'psr4' => true,
+        'random_api_migration' => true,
+        'return_assignment' => false, // Sometimes useful for clarity or debug
+        'return_type_declaration' => true,
+        'self_accessor' => true,
+        'self_static_accessor' => true,
+        'semicolon_after_instruction' => false, // Buggy in `samples/index.php`
+        'set_type_to_cast' => true,
+        'short_scalar_cast' => true,
+        'simple_to_complex_string_variable' => false, // Would differ from TypeScript without obvious advantages
+        'simplified_null_return' => false, // Even if technically correct we prefer to be explicit
+        'single_blank_line_at_eof' => true,
+        'single_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' => 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' => false, // was escape_implicit_backslashes, too confusing
+        'string_line_ending' => true,
+        'switch_case_semicolon_to_colon' => true,
+        'switch_case_space' => true,
+        'ternary_operator_spaces' => true,
+        'ternary_to_null_coalescing' => true,
+        'trailing_comma_in_multiline' => true,
+        'trim_array_spaces' => true,
+        'type_declaration_spaces' => ['elements' => ['function', 'property']], // was function_typehint_space
+        'unary_operator_spaces' => true,
+        'visibility_required' => ['elements' => ['property', 'method']], // not const
+        'void_return' => true,
+        'whitespace_after_comma_in_array' => true,
+        'yoda_style' => false,
+    ]);
+
+return $config;

+ 86 - 0
vendor/phpoffice/phpspreadsheet/phpstan-conditional.php

@@ -0,0 +1,86 @@
+<?php
+
+$config = [];
+
+if (PHP_VERSION_ID < 80000) {
+    // GdImage not available before PHP8
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Method .* has invalid return type GdImage\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Shared/Drawing.php',
+        'count' => 1,
+    ];
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Property .* has unknown class GdImage as its type\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php',
+        'count' => 1,
+    ];
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Method .* has invalid return type GdImage\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php',
+        'count' => 1,
+    ];
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Parameter .* of method .* has invalid type GdImage\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php',
+        'count' => 1,
+    ];
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Class GdImage not found\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Writer/Xls/Worksheet.php',
+        'count' => 1,
+    ];
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Parameter .* of method .* has invalid type GdImage\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Writer/Xls/Worksheet.php',
+        'count' => 1,
+    ];
+    // GdImage with Phpstan 1.9.2
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~Class GdImage not found.*$~',
+        'path' => __DIR__ . '/tests/PhpSpreadsheetTests/Worksheet/MemoryDrawingTest.php',
+        'count' => 3,
+    ];
+    // Erroneous analysis by Phpstan before PHP8 - 3rd parameter is nullable
+    // Fixed for Php7 with Phpstan 1.9.
+    //$config['parameters']['ignoreErrors'][] = [
+    //    'message' => '#^Parameter \\#3 \\$namespace of method XMLWriter\\:\\:startElementNs\\(\\) expects string, null given\\.$#',
+    //    'path' => __DIR__ . '/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php',
+    //    'count' => 8,
+    //];
+    // 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\\.$#',
+        '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<int, mixed>\\|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<int, float\\|int>\\|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<int, float|int>\\|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<int, mixed>\\|false given.$#',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Calculation/TextData/Text.php',
+        'count' => 1,
+    ];
+} else {
+    // Flagged in Php8+ - unsure how to correct code
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '#^Binary operation "/" between float and array[|]float[|]int[|]string results in an error.#',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php',
+        'count' => 1,
+    ];
+}
+
+return $config;

+ 15 - 0
vendor/phpoffice/phpspreadsheet/phpunit10.xml.dist

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd" bootstrap="./tests/bootstrap.php" backupGlobals="true" colors="true" cacheResultFile="/tmp/.phpspreadsheet.phpunit.result.cache">
+  <coverage/>
+  <php>
+    <ini name="memory_limit" value="2048M"/>
+  </php>
+  <testsuite name="PhpSpreadsheet Unit Test Suite">
+    <directory>./tests/PhpSpreadsheetTests</directory>
+  </testsuite>
+  <source>
+    <include>
+      <directory suffix=".php">./src</directory>
+    </include>
+  </source>
+</phpunit>

+ 133 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentHelper;
+use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentProcessor;
+
+trait ArrayEnabled
+{
+    /**
+     * @var ArrayArgumentHelper
+     */
+    private static $arrayArgumentHelper;
+
+    /**
+     * @param array|false $arguments Can be changed to array for Php8.1+
+     */
+    private static function initialiseHelper($arguments): void
+    {
+        if (self::$arrayArgumentHelper === null) {
+            self::$arrayArgumentHelper = new ArrayArgumentHelper();
+        }
+        self::$arrayArgumentHelper->initialise(($arguments === false) ? [] : $arguments);
+    }
+
+    /**
+     * Handles array argument processing when the function accepts a single argument that can be an array argument.
+     * Example use for:
+     *         DAYOFMONTH() or FACT().
+     */
+    protected static function evaluateSingleArgumentArray(callable $method, array $values): array
+    {
+        $result = [];
+        foreach ($values as $value) {
+            $result[] = $method($value);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Handles array argument processing when the function accepts multiple arguments,
+     *     and any of them can be an array argument.
+     * Example use for:
+     *         ROUND() or DATE().
+     *
+     * @param mixed ...$arguments
+     */
+    protected static function evaluateArrayArguments(callable $method, ...$arguments): array
+    {
+        self::initialiseHelper($arguments);
+        $arguments = self::$arrayArgumentHelper->arguments();
+
+        return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
+    }
+
+    /**
+     * Handles array argument processing when the function accepts multiple arguments,
+     *     but only the first few (up to limit) can be an array arguments.
+     * Example use for:
+     *         NETWORKDAYS() or CONCATENATE(), where the last argument is a matrix (or a series of values) that need
+     *                                         to be treated as a such rather than as an array arguments.
+     *
+     * @param mixed ...$arguments
+     */
+    protected static function evaluateArrayArgumentsSubset(callable $method, int $limit, ...$arguments): array
+    {
+        self::initialiseHelper(array_slice($arguments, 0, $limit));
+        $trailingArguments = array_slice($arguments, $limit);
+        $arguments = self::$arrayArgumentHelper->arguments();
+        $arguments = array_merge($arguments, $trailingArguments);
+
+        return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
+    }
+
+    /**
+     * @param mixed $value
+     */
+    private static function testFalse($value): bool
+    {
+        return $value === false;
+    }
+
+    /**
+     * Handles array argument processing when the function accepts multiple arguments,
+     *     but only the last few (from start) can be an array arguments.
+     * Example use for:
+     *         Z.TEST() or INDEX(), where the first argument 1 is a matrix that needs to be treated as a dataset
+     *                   rather than as an array argument.
+     *
+     * @param mixed ...$arguments
+     */
+    protected static function evaluateArrayArgumentsSubsetFrom(callable $method, int $start, ...$arguments): array
+    {
+        $arrayArgumentsSubset = array_combine(
+            range($start, count($arguments) - $start),
+            array_slice($arguments, $start)
+        );
+        if (self::testFalse($arrayArgumentsSubset)) {
+            return ['#VALUE!'];
+        }
+
+        self::initialiseHelper($arrayArgumentsSubset);
+        $leadingArguments = array_slice($arguments, 0, $start);
+        $arguments = self::$arrayArgumentHelper->arguments();
+        $arguments = array_merge($leadingArguments, $arguments);
+
+        return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
+    }
+
+    /**
+     * Handles array argument processing when the function accepts multiple arguments,
+     *     and any of them can be an array argument except for the one specified by ignore.
+     * Example use for:
+     *         HLOOKUP() and VLOOKUP(), where argument 1 is a matrix that needs to be treated as a database
+     *                                  rather than as an array argument.
+     *
+     * @param mixed ...$arguments
+     */
+    protected static function evaluateArrayArgumentsIgnore(callable $method, int $ignore, ...$arguments): array
+    {
+        $leadingArguments = array_slice($arguments, 0, $ignore);
+        $ignoreArgument = array_slice($arguments, $ignore, 1);
+        $trailingArguments = array_slice($arguments, $ignore + 1);
+
+        self::initialiseHelper(array_merge($leadingArguments, [[null]], $trailingArguments));
+        $arguments = self::$arrayArgumentHelper->arguments();
+
+        array_splice($arguments, $ignore, 1, $ignoreArgument);
+
+        return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
+    }
+}

+ 181 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php

@@ -0,0 +1,181 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation;
+
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class BinaryComparison
+{
+    /**
+     * Epsilon Precision used for comparisons in calculations.
+     */
+    private const DELTA = 0.1e-12;
+
+    /**
+     * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.
+     *
+     * @param null|string $str1 First string value for the comparison
+     * @param null|string $str2 Second string value for the comparison
+     */
+    private static function strcmpLowercaseFirst($str1, $str2): int
+    {
+        $inversedStr1 = StringHelper::strCaseReverse($str1 ?? '');
+        $inversedStr2 = StringHelper::strCaseReverse($str2 ?? '');
+
+        return strcmp($inversedStr1, $inversedStr2);
+    }
+
+    /**
+     * PHP8.1 deprecates passing null to strcmp.
+     *
+     * @param null|string $str1 First string value for the comparison
+     * @param null|string $str2 Second string value for the comparison
+     */
+    private static function strcmpAllowNull($str1, $str2): int
+    {
+        return strcmp($str1 ?? '', $str2 ?? '');
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    public static function compare($operand1, $operand2, string $operator): bool
+    {
+        //    Simple validate the two operands if they are string values
+        if (is_string($operand1) && $operand1 > '' && $operand1[0] == Calculation::FORMULA_STRING_QUOTE) {
+            $operand1 = Calculation::unwrapResult($operand1);
+        }
+        if (is_string($operand2) && $operand2 > '' && $operand2[0] == Calculation::FORMULA_STRING_QUOTE) {
+            $operand2 = Calculation::unwrapResult($operand2);
+        }
+
+        // Use case insensitive comparaison if not OpenOffice mode
+        if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) {
+            if (is_string($operand1)) {
+                $operand1 = StringHelper::strToUpper($operand1);
+            }
+            if (is_string($operand2)) {
+                $operand2 = StringHelper::strToUpper($operand2);
+            }
+        }
+
+        $useLowercaseFirstComparison = is_string($operand1) &&
+            is_string($operand2) &&
+            Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE;
+
+        return self::evaluateComparison($operand1, $operand2, $operator, $useLowercaseFirstComparison);
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function evaluateComparison($operand1, $operand2, string $operator, bool $useLowercaseFirstComparison): bool
+    {
+        switch ($operator) {
+            //    Equality
+            case '=':
+                return self::equal($operand1, $operand2);
+            //    Greater than
+            case '>':
+                return self::greaterThan($operand1, $operand2, $useLowercaseFirstComparison);
+            //    Less than
+            case '<':
+                return self::lessThan($operand1, $operand2, $useLowercaseFirstComparison);
+            //    Greater than or equal
+            case '>=':
+                return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison);
+            //    Less than or equal
+            case '<=':
+                return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison);
+            //    Inequality
+            case '<>':
+                return self::notEqual($operand1, $operand2);
+            default:
+                throw new Exception('Unsupported binary comparison operator');
+        }
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function equal($operand1, $operand2): bool
+    {
+        if (is_numeric($operand1) && is_numeric($operand2)) {
+            $result = (abs($operand1 - $operand2) < self::DELTA);
+        } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
+            $result = $operand1 == $operand2;
+        } else {
+            $result = self::strcmpAllowNull($operand1, $operand2) == 0;
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function greaterThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool
+    {
+        if (is_numeric($operand1) && is_numeric($operand2)) {
+            $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 > $operand2));
+        } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
+            $result = $operand1 >= $operand2;
+        } elseif ($useLowercaseFirstComparison) {
+            $result = self::strcmpLowercaseFirst($operand1, $operand2) >= 0;
+        } else {
+            $result = self::strcmpAllowNull($operand1, $operand2) >= 0;
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function lessThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool
+    {
+        if (is_numeric($operand1) && is_numeric($operand2)) {
+            $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 < $operand2));
+        } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
+            $result = $operand1 <= $operand2;
+        } elseif ($useLowercaseFirstComparison) {
+            $result = self::strcmpLowercaseFirst($operand1, $operand2) <= 0;
+        } else {
+            $result = self::strcmpAllowNull($operand1, $operand2) <= 0;
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function greaterThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool
+    {
+        return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function lessThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool
+    {
+        return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function notEqual($operand1, $operand2): bool
+    {
+        return self::equal($operand1, $operand2) !== true;
+    }
+}

+ 209 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php

@@ -0,0 +1,209 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+
+class ArrayArgumentHelper
+{
+    /**
+     * @var int
+     */
+    protected $indexStart = 0;
+
+    /**
+     * @var array
+     */
+    protected $arguments;
+
+    /**
+     * @var int
+     */
+    protected $argumentCount;
+
+    /**
+     * @var array
+     */
+    protected $rows;
+
+    /**
+     * @var array
+     */
+    protected $columns;
+
+    public function initialise(array $arguments): void
+    {
+        $keys = array_keys($arguments);
+        $this->indexStart = (int) array_shift($keys);
+        $this->rows = $this->rows($arguments);
+        $this->columns = $this->columns($arguments);
+
+        $this->argumentCount = count($arguments);
+        $this->arguments = $this->flattenSingleCellArrays($arguments, $this->rows, $this->columns);
+
+        $this->rows = $this->rows($arguments);
+        $this->columns = $this->columns($arguments);
+
+        if ($this->arrayArguments() > 2) {
+            throw new Exception('Formulae with more than two array arguments are not supported');
+        }
+    }
+
+    public function arguments(): array
+    {
+        return $this->arguments;
+    }
+
+    public function hasArrayArgument(): bool
+    {
+        return $this->arrayArguments() > 0;
+    }
+
+    public function getFirstArrayArgumentNumber(): int
+    {
+        $rowArrays = $this->filterArray($this->rows);
+        $columnArrays = $this->filterArray($this->columns);
+
+        for ($index = $this->indexStart; $index < $this->argumentCount; ++$index) {
+            if (isset($rowArrays[$index]) || isset($columnArrays[$index])) {
+                return ++$index;
+            }
+        }
+
+        return 0;
+    }
+
+    public function getSingleRowVector(): ?int
+    {
+        $rowVectors = $this->getRowVectors();
+
+        return count($rowVectors) === 1 ? array_pop($rowVectors) : null;
+    }
+
+    private function getRowVectors(): array
+    {
+        $rowVectors = [];
+        for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) {
+            if ($this->rows[$index] === 1 && $this->columns[$index] > 1) {
+                $rowVectors[] = $index;
+            }
+        }
+
+        return $rowVectors;
+    }
+
+    public function getSingleColumnVector(): ?int
+    {
+        $columnVectors = $this->getColumnVectors();
+
+        return count($columnVectors) === 1 ? array_pop($columnVectors) : null;
+    }
+
+    private function getColumnVectors(): array
+    {
+        $columnVectors = [];
+        for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) {
+            if ($this->rows[$index] > 1 && $this->columns[$index] === 1) {
+                $columnVectors[] = $index;
+            }
+        }
+
+        return $columnVectors;
+    }
+
+    public function getMatrixPair(): array
+    {
+        for ($i = $this->indexStart; $i < ($this->indexStart + $this->argumentCount - 1); ++$i) {
+            for ($j = $i + 1; $j < $this->argumentCount; ++$j) {
+                if (isset($this->rows[$i], $this->rows[$j])) {
+                    return [$i, $j];
+                }
+            }
+        }
+
+        return [];
+    }
+
+    public function isVector(int $argument): bool
+    {
+        return $this->rows[$argument] === 1 || $this->columns[$argument] === 1;
+    }
+
+    public function isRowVector(int $argument): bool
+    {
+        return $this->rows[$argument] === 1;
+    }
+
+    public function isColumnVector(int $argument): bool
+    {
+        return $this->columns[$argument] === 1;
+    }
+
+    public function rowCount(int $argument): int
+    {
+        return $this->rows[$argument];
+    }
+
+    public function columnCount(int $argument): int
+    {
+        return $this->columns[$argument];
+    }
+
+    private function rows(array $arguments): array
+    {
+        return array_map(
+            function ($argument) {
+                return is_countable($argument) ? count($argument) : 1;
+            },
+            $arguments
+        );
+    }
+
+    private function columns(array $arguments): array
+    {
+        return array_map(
+            function ($argument) {
+                return is_array($argument) && is_array($argument[array_keys($argument)[0]])
+                    ? count($argument[array_keys($argument)[0]])
+                    : 1;
+            },
+            $arguments
+        );
+    }
+
+    public function arrayArguments(): int
+    {
+        $count = 0;
+        foreach (array_keys($this->arguments) as $argument) {
+            if ($this->rows[$argument] > 1 || $this->columns[$argument] > 1) {
+                ++$count;
+            }
+        }
+
+        return $count;
+    }
+
+    private function flattenSingleCellArrays(array $arguments, array $rows, array $columns): array
+    {
+        foreach ($arguments as $index => $argument) {
+            if ($rows[$index] === 1 && $columns[$index] === 1) {
+                while (is_array($argument)) {
+                    $argument = array_pop($argument);
+                }
+                $arguments[$index] = $argument;
+            }
+        }
+
+        return $arguments;
+    }
+
+    private function filterArray(array $array): array
+    {
+        return array_filter(
+            $array,
+            function ($value) {
+                return $value > 1;
+            }
+        );
+    }
+}

+ 175 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php

@@ -0,0 +1,175 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class ArrayArgumentProcessor
+{
+    /**
+     * @var ArrayArgumentHelper
+     */
+    private static $arrayArgumentHelper;
+
+    /**
+     * @param mixed ...$arguments
+     */
+    public static function processArguments(
+        ArrayArgumentHelper $arrayArgumentHelper,
+        callable $method,
+        ...$arguments
+    ): array {
+        self::$arrayArgumentHelper = $arrayArgumentHelper;
+
+        if (self::$arrayArgumentHelper->hasArrayArgument() === false) {
+            return [$method(...$arguments)];
+        }
+
+        if (self::$arrayArgumentHelper->arrayArguments() === 1) {
+            $nthArgument = self::$arrayArgumentHelper->getFirstArrayArgumentNumber();
+
+            return self::evaluateNthArgumentAsArray($method, $nthArgument, ...$arguments);
+        }
+
+        $singleRowVectorIndex = self::$arrayArgumentHelper->getSingleRowVector();
+        $singleColumnVectorIndex = self::$arrayArgumentHelper->getSingleColumnVector();
+
+        if ($singleRowVectorIndex !== null && $singleColumnVectorIndex !== null) {
+            // Basic logic for a single row vector and a single column vector
+            return self::evaluateVectorPair($method, $singleRowVectorIndex, $singleColumnVectorIndex, ...$arguments);
+        }
+
+        $matrixPair = self::$arrayArgumentHelper->getMatrixPair();
+        if ($matrixPair !== []) {
+            if (
+                (self::$arrayArgumentHelper->isVector($matrixPair[0]) === true &&
+                    self::$arrayArgumentHelper->isVector($matrixPair[1]) === false) ||
+                (self::$arrayArgumentHelper->isVector($matrixPair[0]) === false &&
+                    self::$arrayArgumentHelper->isVector($matrixPair[1]) === true)
+            ) {
+                // Logic for a matrix and a vector (row or column)
+                return self::evaluateVectorMatrixPair($method, $matrixPair, ...$arguments);
+            }
+            // Logic for matrix/matrix, column vector/column vector or row vector/row vector
+            return self::evaluateMatrixPair($method, $matrixPair, ...$arguments);
+        }
+
+        // Still need to work out the logic for more than two array arguments,
+        // For the moment, we're throwing an Exception when we initialise the ArrayArgumentHelper
+        return ['#VALUE!'];
+    }
+
+    /**
+     * @param mixed ...$arguments
+     */
+    private static function evaluateVectorMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
+    {
+        $matrix2 = array_pop($matrixIndexes);
+        /** @var array $matrixValues2 */
+        $matrixValues2 = $arguments[$matrix2];
+        $matrix1 = array_pop($matrixIndexes);
+        /** @var array $matrixValues1 */
+        $matrixValues1 = $arguments[$matrix1];
+
+        $rows = min(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
+        $columns = min(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
+
+        if ($rows === 1) {
+            $rows = max(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
+        }
+        if ($columns === 1) {
+            $columns = max(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
+        }
+
+        $result = [];
+        for ($rowIndex = 0; $rowIndex < $rows; ++$rowIndex) {
+            for ($columnIndex = 0; $columnIndex < $columns; ++$columnIndex) {
+                $rowIndex1 = self::$arrayArgumentHelper->isRowVector($matrix1) ? 0 : $rowIndex;
+                $columnIndex1 = self::$arrayArgumentHelper->isColumnVector($matrix1) ? 0 : $columnIndex;
+                $value1 = $matrixValues1[$rowIndex1][$columnIndex1];
+                $rowIndex2 = self::$arrayArgumentHelper->isRowVector($matrix2) ? 0 : $rowIndex;
+                $columnIndex2 = self::$arrayArgumentHelper->isColumnVector($matrix2) ? 0 : $columnIndex;
+                $value2 = $matrixValues2[$rowIndex2][$columnIndex2];
+                $arguments[$matrix1] = $value1;
+                $arguments[$matrix2] = $value2;
+
+                $result[$rowIndex][$columnIndex] = $method(...$arguments);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param mixed ...$arguments
+     */
+    private static function evaluateMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
+    {
+        $matrix2 = array_pop($matrixIndexes);
+        /** @var array $matrixValues2 */
+        $matrixValues2 = $arguments[$matrix2];
+        $matrix1 = array_pop($matrixIndexes);
+        /** @var array $matrixValues1 */
+        $matrixValues1 = $arguments[$matrix1];
+
+        $result = [];
+        foreach ($matrixValues1 as $rowIndex => $row) {
+            foreach ($row as $columnIndex => $value1) {
+                if (isset($matrixValues2[$rowIndex][$columnIndex]) === false) {
+                    continue;
+                }
+
+                $value2 = $matrixValues2[$rowIndex][$columnIndex];
+                $arguments[$matrix1] = $value1;
+                $arguments[$matrix2] = $value2;
+
+                $result[$rowIndex][$columnIndex] = $method(...$arguments);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param mixed ...$arguments
+     */
+    private static function evaluateVectorPair(callable $method, int $rowIndex, int $columnIndex, ...$arguments): array
+    {
+        $rowVector = Functions::flattenArray($arguments[$rowIndex]);
+        $columnVector = Functions::flattenArray($arguments[$columnIndex]);
+
+        $result = [];
+        foreach ($columnVector as $column) {
+            $rowResults = [];
+            foreach ($rowVector as $row) {
+                $arguments[$rowIndex] = $row;
+                $arguments[$columnIndex] = $column;
+
+                $rowResults[] = $method(...$arguments);
+            }
+            $result[] = $rowResults;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Note, offset is from 1 (for the first argument) rather than from 0.
+     *
+     * @param mixed ...$arguments
+     */
+    private static function evaluateNthArgumentAsArray(callable $method, int $nthArgument, ...$arguments): array
+    {
+        $values = array_slice($arguments, $nthArgument - 1, 1);
+        /** @var array $values */
+        $values = array_pop($values);
+
+        $result = [];
+        foreach ($values as $value) {
+            $arguments[$nthArgument - 1] = $value;
+            $result[] = $method(...$arguments);
+        }
+
+        return $result;
+    }
+}

+ 223 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/BranchPruner.php

@@ -0,0 +1,223 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+
+class BranchPruner
+{
+    /**
+     * @var bool
+     */
+    protected $branchPruningEnabled = true;
+
+    /**
+     * Used to generate unique store keys.
+     *
+     * @var int
+     */
+    private $branchStoreKeyCounter = 0;
+
+    /**
+     * currently pending storeKey (last item of the storeKeysStack.
+     *
+     * @var ?string
+     */
+    protected $pendingStoreKey;
+
+    /**
+     * @var string[]
+     */
+    protected $storeKeysStack = [];
+
+    /**
+     * @var bool[]
+     */
+    protected $conditionMap = [];
+
+    /**
+     * @var bool[]
+     */
+    protected $thenMap = [];
+
+    /**
+     * @var bool[]
+     */
+    protected $elseMap = [];
+
+    /**
+     * @var int[]
+     */
+    protected $braceDepthMap = [];
+
+    /**
+     * @var null|string
+     */
+    protected $currentCondition;
+
+    /**
+     * @var null|string
+     */
+    protected $currentOnlyIf;
+
+    /**
+     * @var null|string
+     */
+    protected $currentOnlyIfNot;
+
+    /**
+     * @var null|string
+     */
+    protected $previousStoreKey;
+
+    public function __construct(bool $branchPruningEnabled)
+    {
+        $this->branchPruningEnabled = $branchPruningEnabled;
+    }
+
+    public function clearBranchStore(): void
+    {
+        $this->branchStoreKeyCounter = 0;
+    }
+
+    public function initialiseForLoop(): void
+    {
+        $this->currentCondition = null;
+        $this->currentOnlyIf = null;
+        $this->currentOnlyIfNot = null;
+        $this->previousStoreKey = null;
+        $this->pendingStoreKey = empty($this->storeKeysStack) ? null : end($this->storeKeysStack);
+
+        if ($this->branchPruningEnabled) {
+            $this->initialiseCondition();
+            $this->initialiseThen();
+            $this->initialiseElse();
+        }
+    }
+
+    private function initialiseCondition(): void
+    {
+        if (isset($this->conditionMap[$this->pendingStoreKey]) && $this->conditionMap[$this->pendingStoreKey]) {
+            $this->currentCondition = $this->pendingStoreKey;
+            $stackDepth = count($this->storeKeysStack);
+            if ($stackDepth > 1) {
+                // nested if
+                $this->previousStoreKey = $this->storeKeysStack[$stackDepth - 2];
+            }
+        }
+    }
+
+    private function initialiseThen(): void
+    {
+        if (isset($this->thenMap[$this->pendingStoreKey]) && $this->thenMap[$this->pendingStoreKey]) {
+            $this->currentOnlyIf = $this->pendingStoreKey;
+        } elseif (
+            isset($this->previousStoreKey, $this->thenMap[$this->previousStoreKey])
+            && $this->thenMap[$this->previousStoreKey]
+        ) {
+            $this->currentOnlyIf = $this->previousStoreKey;
+        }
+    }
+
+    private function initialiseElse(): void
+    {
+        if (isset($this->elseMap[$this->pendingStoreKey]) && $this->elseMap[$this->pendingStoreKey]) {
+            $this->currentOnlyIfNot = $this->pendingStoreKey;
+        } elseif (
+            isset($this->previousStoreKey, $this->elseMap[$this->previousStoreKey])
+            && $this->elseMap[$this->previousStoreKey]
+        ) {
+            $this->currentOnlyIfNot = $this->previousStoreKey;
+        }
+    }
+
+    public function decrementDepth(): void
+    {
+        if (!empty($this->pendingStoreKey)) {
+            --$this->braceDepthMap[$this->pendingStoreKey];
+        }
+    }
+
+    public function incrementDepth(): void
+    {
+        if (!empty($this->pendingStoreKey)) {
+            ++$this->braceDepthMap[$this->pendingStoreKey];
+        }
+    }
+
+    public function functionCall(string $functionName): void
+    {
+        if ($this->branchPruningEnabled && ($functionName === 'IF(')) {
+            // we handle a new if
+            $this->pendingStoreKey = $this->getUnusedBranchStoreKey();
+            $this->storeKeysStack[] = $this->pendingStoreKey;
+            $this->conditionMap[$this->pendingStoreKey] = true;
+            $this->braceDepthMap[$this->pendingStoreKey] = 0;
+        } elseif (!empty($this->pendingStoreKey) && array_key_exists($this->pendingStoreKey, $this->braceDepthMap)) {
+            // this is not an if but we go deeper
+            ++$this->braceDepthMap[$this->pendingStoreKey];
+        }
+    }
+
+    public function argumentSeparator(): void
+    {
+        if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === 0) {
+            // We must go to the IF next argument
+            if ($this->conditionMap[$this->pendingStoreKey]) {
+                $this->conditionMap[$this->pendingStoreKey] = false;
+                $this->thenMap[$this->pendingStoreKey] = true;
+            } elseif ($this->thenMap[$this->pendingStoreKey]) {
+                $this->thenMap[$this->pendingStoreKey] = false;
+                $this->elseMap[$this->pendingStoreKey] = true;
+            } elseif ($this->elseMap[$this->pendingStoreKey]) {
+                throw new Exception('Reaching fourth argument of an IF');
+            }
+        }
+    }
+
+    /**
+     * @param mixed $value
+     */
+    public function closingBrace($value): void
+    {
+        if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === -1) {
+            // we are closing an IF(
+            if ($value !== 'IF(') {
+                throw new Exception('Parser bug we should be in an "IF("');
+            }
+
+            if ($this->conditionMap[$this->pendingStoreKey]) {
+                throw new Exception('We should not be expecting a condition');
+            }
+
+            $this->thenMap[$this->pendingStoreKey] = false;
+            $this->elseMap[$this->pendingStoreKey] = false;
+            --$this->braceDepthMap[$this->pendingStoreKey];
+            array_pop($this->storeKeysStack);
+            $this->pendingStoreKey = null;
+        }
+    }
+
+    public function currentCondition(): ?string
+    {
+        return $this->currentCondition;
+    }
+
+    public function currentOnlyIf(): ?string
+    {
+        return $this->currentOnlyIf;
+    }
+
+    public function currentOnlyIfNot(): ?string
+    {
+        return $this->currentOnlyIfNot;
+    }
+
+    private function getUnusedBranchStoreKey(): string
+    {
+        $storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter;
+        ++$this->branchStoreKeyCounter;
+
+        return $storeKeyValue;
+    }
+}

+ 147 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php

@@ -0,0 +1,147 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class FormattedNumber
+{
+    /**    Constants                */
+    /**    Regular Expressions        */
+    private const STRING_REGEXP_FRACTION = '~^\s*(-?)((\d*)\s+)?(\d+\/\d+)\s*$~';
+
+    private const STRING_REGEXP_PERCENT = '~^(?:(?: *(?<PrefixedSign>[-+])? *\% *(?<PrefixedSign2>[-+])? *(?<PrefixedValue>[0-9]+\.?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?<PostfixedSign>[-+])? *(?<PostfixedValue>[0-9]+\.?[0-9]*(?:E[-+]?[0-9]*)?) *\% *))$~i';
+
+    // preg_quoted string for major currency symbols, with a %s for locale currency
+    private const CURRENCY_CONVERSION_LIST = '\$€£¥%s';
+
+    private const STRING_CONVERSION_LIST = [
+        [self::class, 'convertToNumberIfNumeric'],
+        [self::class, 'convertToNumberIfFraction'],
+        [self::class, 'convertToNumberIfPercent'],
+        [self::class, 'convertToNumberIfCurrency'],
+    ];
+
+    /**
+     * Identify whether a string contains a formatted numeric value,
+     * and convert it to a numeric if it is.
+     *
+     * @param string $operand string value to test
+     */
+    public static function convertToNumberIfFormatted(string &$operand): bool
+    {
+        foreach (self::STRING_CONVERSION_LIST as $conversionMethod) {
+            if ($conversionMethod($operand) === true) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Identify whether a string contains a numeric value,
+     * and convert it to a numeric if it is.
+     *
+     * @param string $operand string value to test
+     */
+    public static function convertToNumberIfNumeric(string &$operand): bool
+    {
+        $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(), '/');
+        $value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
+
+        if (is_numeric($value)) {
+            $operand = (float) $value;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Identify whether a string contains a fractional numeric value,
+     * and convert it to a numeric if it is.
+     *
+     * @param string $operand string value to test
+     */
+    public static function convertToNumberIfFraction(string &$operand): bool
+    {
+        if (preg_match(self::STRING_REGEXP_FRACTION, $operand, $match)) {
+            $sign = ($match[1] === '-') ? '-' : '+';
+            $wholePart = ($match[3] === '') ? '' : ($sign . $match[3]);
+            $fractionFormula = '=' . $wholePart . $sign . $match[4];
+            $operand = Calculation::getInstance()->_calculateFormulaValue($fractionFormula);
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Identify whether a string contains a percentage, and if so,
+     * convert it to a numeric.
+     *
+     * @param string $operand string value to test
+     */
+    public static function convertToNumberIfPercent(string &$operand): bool
+    {
+        $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
+        $value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', trim($operand));
+        $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
+        $value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
+
+        $match = [];
+        if ($value !== null && preg_match(self::STRING_REGEXP_PERCENT, $value, $match, PREG_UNMATCHED_AS_NULL)) {
+            //Calculate the percentage
+            $sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? '';
+            $operand = (float) ($sign . ($match['PostfixedValue'] ?? $match['PrefixedValue'])) / 100;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Identify whether a string contains a currency value, and if so,
+     * convert it to a numeric.
+     *
+     * @param string $operand string value to test
+     */
+    public static function convertToNumberIfCurrency(string &$operand): bool
+    {
+        $currencyRegexp = self::currencyMatcherRegexp();
+        $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
+            $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 false;
+    }
+
+    public static function currencyMatcherRegexp(): string
+    {
+        $currencyCodes = sprintf(self::CURRENCY_CONVERSION_LIST, preg_quote(StringHelper::getCurrencyCode(), '/'));
+        $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
+
+        return '~^(?:(?: *(?<PrefixedSign>[-+])? *(?<PrefixedCurrency>[' . $currencyCodes . ']) *(?<PrefixedSign2>[-+])? *(?<PrefixedValue>[0-9]+[' . $decimalSeparator . ']?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?<PostfixedSign>[-+])? *(?<PostfixedValue>[0-9]+' . $decimalSeparator . '?[0-9]*(?:E[-+]?[0-9]*)?) *(?<PostfixedCurrency>[' . $currencyCodes . ']) *))$~ui';
+    }
+}

+ 10 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engine\Operands;
+
+interface Operand
+{
+    public static function fromParser(string $formula, int $index, array $matches): self;
+
+    public function value(): string;
+}

+ 344 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php

@@ -0,0 +1,344 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engine\Operands;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Worksheet\Table;
+
+final class StructuredReference implements Operand
+{
+    public const NAME = 'Structured Reference';
+
+    private const OPEN_BRACE = '[';
+    private const CLOSE_BRACE = ']';
+
+    private const ITEM_SPECIFIER_ALL = '#All';
+    private const ITEM_SPECIFIER_HEADERS = '#Headers';
+    private const ITEM_SPECIFIER_DATA = '#Data';
+    private const ITEM_SPECIFIER_TOTALS = '#Totals';
+    private const ITEM_SPECIFIER_THIS_ROW = '#This Row';
+
+    private const ITEM_SPECIFIER_ROWS_SET = [
+        self::ITEM_SPECIFIER_ALL,
+        self::ITEM_SPECIFIER_HEADERS,
+        self::ITEM_SPECIFIER_DATA,
+        self::ITEM_SPECIFIER_TOTALS,
+    ];
+
+    private const TABLE_REFERENCE = '/([\p{L}_\\\\][\p{L}\p{N}\._]+)?(\[(?:[^\]\[]+|(?R))*+\])/miu';
+
+    private string $value;
+
+    private string $tableName;
+
+    private Table $table;
+
+    private string $reference;
+
+    private ?int $headersRow;
+
+    private int $firstDataRow;
+
+    private int $lastDataRow;
+
+    private ?int $totalsRow;
+
+    private array $columns;
+
+    public function __construct(string $structuredReference)
+    {
+        $this->value = $structuredReference;
+    }
+
+    public static function fromParser(string $formula, int $index, array $matches): self
+    {
+        $val = $matches[0];
+
+        $srCount = substr_count($val, self::OPEN_BRACE)
+            - substr_count($val, self::CLOSE_BRACE);
+        while ($srCount > 0) {
+            $srIndex = strlen($val);
+            $srStringRemainder = substr($formula, $index + $srIndex);
+            $closingPos = strpos($srStringRemainder, self::CLOSE_BRACE);
+            if ($closingPos === false) {
+                throw new Exception("Formula Error: No closing ']' to match opening '['");
+            }
+            $srStringRemainder = substr($srStringRemainder, 0, $closingPos + 1);
+            --$srCount;
+            if (strpos($srStringRemainder, self::OPEN_BRACE) !== false) {
+                ++$srCount;
+            }
+            $val .= $srStringRemainder;
+        }
+
+        return new self($val);
+    }
+
+    /**
+     * @throws Exception
+     * @throws \PhpOffice\PhpSpreadsheet\Exception
+     */
+    public function parse(Cell $cell): string
+    {
+        $this->getTableStructure($cell);
+        $cellRange = ($this->isRowReference()) ? $this->getRowReference($cell) : $this->getColumnReference();
+
+        return $cellRange;
+    }
+
+    private function isRowReference(): bool
+    {
+        return strpos($this->value, '[@') !== false
+            || strpos($this->value, '[' . self::ITEM_SPECIFIER_THIS_ROW . ']') !== false;
+    }
+
+    /**
+     * @throws Exception
+     * @throws \PhpOffice\PhpSpreadsheet\Exception
+     */
+    private function getTableStructure(Cell $cell): void
+    {
+        preg_match(self::TABLE_REFERENCE, $this->value, $matches);
+
+        $this->tableName = $matches[1];
+        $this->table = ($this->tableName === '')
+            ? $this->getTableForCell($cell)
+            : $this->getTableByName($cell);
+        $this->reference = $matches[2];
+        $tableRange = Coordinate::getRangeBoundaries($this->table->getRange());
+
+        $this->headersRow = ($this->table->getShowHeaderRow()) ? (int) $tableRange[0][1] : null;
+        $this->firstDataRow = ($this->table->getShowHeaderRow()) ? (int) $tableRange[0][1] + 1 : $tableRange[0][1];
+        $this->totalsRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] : null;
+        $this->lastDataRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] - 1 : $tableRange[1][1];
+
+        $this->columns = $this->getColumns($cell, $tableRange);
+    }
+
+    /**
+     * @throws Exception
+     * @throws \PhpOffice\PhpSpreadsheet\Exception
+     */
+    private function getTableForCell(Cell $cell): Table
+    {
+        $tables = $cell->getWorksheet()->getTableCollection();
+        foreach ($tables as $table) {
+            /** @var Table $table */
+            $range = $table->getRange();
+            if ($cell->isInRange($range) === true) {
+                $this->tableName = $table->getName();
+
+                return $table;
+            }
+        }
+
+        throw new Exception('Table for Structured Reference cannot be identified');
+    }
+
+    /**
+     * @throws Exception
+     * @throws \PhpOffice\PhpSpreadsheet\Exception
+     */
+    private function getTableByName(Cell $cell): Table
+    {
+        $table = $cell->getWorksheet()->getTableByName($this->tableName);
+
+        if ($table === null) {
+            throw new Exception("Table {$this->tableName} for Structured Reference cannot be located");
+        }
+
+        return $table;
+    }
+
+    private function getColumns(Cell $cell, array $tableRange): array
+    {
+        $worksheet = $cell->getWorksheet();
+        $cellReference = $cell->getCoordinate();
+
+        $columns = [];
+        $lastColumn = ++$tableRange[1][0];
+        for ($column = $tableRange[0][0]; $column !== $lastColumn; ++$column) {
+            $columns[$column] = $worksheet
+                ->getCell($column . ($this->headersRow ?? ($this->firstDataRow - 1)))
+                ->getCalculatedValue();
+        }
+
+        $worksheet->getCell($cellReference);
+
+        return $columns;
+    }
+
+    private function getRowReference(Cell $cell): string
+    {
+        $reference = str_replace("\u{a0}", ' ', $this->reference);
+        /** @var string $reference */
+        $reference = str_replace('[' . self::ITEM_SPECIFIER_THIS_ROW . '],', '', $reference);
+
+        foreach ($this->columns as $columnId => $columnName) {
+            $columnName = str_replace("\u{a0}", ' ', $columnName);
+            $reference = $this->adjustRowReference($columnName, $reference, $cell, $columnId);
+        }
+
+        /** @var string $reference */
+        return $this->validateParsedReference(trim($reference, '[]@, '));
+    }
+
+    private function adjustRowReference(string $columnName, string $reference, Cell $cell, string $columnId): string
+    {
+        if ($columnName !== '') {
+            $cellReference = $columnId . $cell->getRow();
+            $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) {
+                $reference = preg_replace($pattern2, $cellReference, $reference);
+            }
+            /** @var string $reference */
+        }
+
+        return $reference;
+    }
+
+    /**
+     * @throws Exception
+     * @throws \PhpOffice\PhpSpreadsheet\Exception
+     */
+    private function getColumnReference(): string
+    {
+        $reference = str_replace("\u{a0}", ' ', $this->reference);
+        $startRow = ($this->totalsRow === null) ? $this->lastDataRow : $this->totalsRow;
+        $endRow = ($this->headersRow === null) ? $this->firstDataRow : $this->headersRow;
+
+        [$startRow, $endRow] = $this->getRowsForColumnReference($reference, $startRow, $endRow);
+        $reference = $this->getColumnsForColumnReference($reference, $startRow, $endRow);
+
+        $reference = trim($reference, '[]@, ');
+        if (substr_count($reference, ':') > 1) {
+            $cells = explode(':', $reference);
+            $firstCell = array_shift($cells);
+            $lastCell = array_pop($cells);
+            $reference = "{$firstCell}:{$lastCell}";
+        }
+
+        return $this->validateParsedReference($reference);
+    }
+
+    /**
+     * @throws Exception
+     * @throws \PhpOffice\PhpSpreadsheet\Exception
+     */
+    private function validateParsedReference(string $reference): string
+    {
+        if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . ':' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $reference) !== 1) {
+            if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $reference) !== 1) {
+                throw new Exception(
+                    "Invalid Structured Reference {$this->reference} {$reference}",
+                    Exception::CALCULATION_ENGINE_PUSH_TO_STACK
+                );
+            }
+        }
+
+        return $reference;
+    }
+
+    private function fullData(int $startRow, int $endRow): string
+    {
+        $columns = array_keys($this->columns);
+        $firstColumn = array_shift($columns);
+        $lastColumn = (empty($columns)) ? $firstColumn : array_pop($columns);
+
+        return "{$firstColumn}{$startRow}:{$lastColumn}{$endRow}";
+    }
+
+    private function getMinimumRow(string $reference): int
+    {
+        switch ($reference) {
+            case self::ITEM_SPECIFIER_ALL:
+            case self::ITEM_SPECIFIER_HEADERS:
+                return $this->headersRow ?? $this->firstDataRow;
+            case self::ITEM_SPECIFIER_DATA:
+                return $this->firstDataRow;
+            case self::ITEM_SPECIFIER_TOTALS:
+                return $this->totalsRow ?? $this->lastDataRow;
+        }
+
+        return $this->headersRow ?? $this->firstDataRow;
+    }
+
+    private function getMaximumRow(string $reference): int
+    {
+        switch ($reference) {
+            case self::ITEM_SPECIFIER_HEADERS:
+                return $this->headersRow ?? $this->firstDataRow;
+            case self::ITEM_SPECIFIER_DATA:
+                return $this->lastDataRow;
+            case self::ITEM_SPECIFIER_ALL:
+            case self::ITEM_SPECIFIER_TOTALS:
+                return $this->totalsRow ?? $this->lastDataRow;
+        }
+
+        return $this->totalsRow ?? $this->lastDataRow;
+    }
+
+    public function value(): string
+    {
+        return $this->value;
+    }
+
+    /**
+     * @return array<int, int>
+     */
+    private function getRowsForColumnReference(string &$reference, int $startRow, int $endRow): array
+    {
+        $rowsSelected = false;
+        foreach (self::ITEM_SPECIFIER_ROWS_SET as $rowReference) {
+            $pattern = '/\[' . $rowReference . '\]/mui';
+            /** @var string $reference */
+            if (preg_match($pattern, $reference) === 1) {
+                if (($rowReference === self::ITEM_SPECIFIER_HEADERS) && ($this->table->getShowHeaderRow() === false)) {
+                    throw new Exception(
+                        'Table Headers are Hidden, and should not be Referenced',
+                        Exception::CALCULATION_ENGINE_PUSH_TO_STACK
+                    );
+                }
+                $rowsSelected = true;
+                $startRow = min($startRow, $this->getMinimumRow($rowReference));
+                $endRow = max($endRow, $this->getMaximumRow($rowReference));
+                $reference = preg_replace($pattern, '', $reference);
+            }
+        }
+        if ($rowsSelected === false) {
+            // If there isn't any Special Item Identifier specified, then the selection defaults to data rows only.
+            $startRow = $this->firstDataRow;
+            $endRow = $this->lastDataRow;
+        }
+
+        return [$startRow, $endRow];
+    }
+
+    private function getColumnsForColumnReference(string $reference, int $startRow, int $endRow): string
+    {
+        $columnsSelected = false;
+        foreach ($this->columns as $columnId => $columnName) {
+            $columnName = str_replace("\u{a0}", ' ', $columnName);
+            $cellFrom = "{$columnId}{$startRow}";
+            $cellTo = "{$columnId}{$endRow}";
+            $cellReference = ($cellFrom === $cellTo) ? $cellFrom : "{$cellFrom}:{$cellTo}";
+            $pattern = '/\[' . preg_quote($columnName, '/') . '\]/mui';
+            if (preg_match($pattern, $reference) === 1) {
+                $columnsSelected = true;
+                $reference = preg_replace($pattern, $cellReference, $reference);
+            }
+            /** @var string $reference */
+        }
+        if ($columnsSelected === false) {
+            return $this->fullData($startRow, $endRow);
+        }
+
+        return $reference;
+    }
+}

+ 71 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Information;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+
+class ErrorValue
+{
+    use ArrayEnabled;
+
+    /**
+     * IS_ERR.
+     *
+     * @param mixed $value Value to check
+     *                      Or can be an array of values
+     *
+     * @return array|bool
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function isErr($value = '')
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        return self::isError($value) && (!self::isNa(($value)));
+    }
+
+    /**
+     * IS_ERROR.
+     *
+     * @param mixed $value Value to check
+     *                      Or can be an array of values
+     *
+     * @return array|bool
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function isError($value = '')
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        if (!is_string($value)) {
+            return false;
+        }
+
+        return in_array($value, ExcelError::ERROR_CODES, true);
+    }
+
+    /**
+     * IS_NA.
+     *
+     * @param mixed $value Value to check
+     *                      Or can be an array of values
+     *
+     * @return array|bool
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function isNa($value = '')
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        return $value === ExcelError::NA();
+    }
+}

+ 171 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ExcelError.php

@@ -0,0 +1,171 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Information;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+
+class ExcelError
+{
+    use ArrayEnabled;
+
+    /**
+     * List of error codes.
+     *
+     * @var array<string, string>
+     */
+    public const ERROR_CODES = [
+        'null' => '#NULL!', // 1
+        'divisionbyzero' => '#DIV/0!', // 2
+        'value' => '#VALUE!', // 3
+        'reference' => '#REF!', // 4
+        'name' => '#NAME?', // 5
+        'num' => '#NUM!', // 6
+        'na' => '#N/A', // 7
+        'gettingdata' => '#GETTING_DATA', // 8
+        'spill' => '#SPILL!', // 9
+        'connect' => '#CONNECT!', //10
+        'blocked' => '#BLOCKED!', //11
+        'unknown' => '#UNKNOWN!', //12
+        'field' => '#FIELD!', //13
+        'calculation' => '#CALC!', //14
+    ];
+
+    /**
+     * List of error codes. Replaced by constant;
+     * previously it was public and updateable, allowing
+     * user to make inappropriate alterations.
+     *
+     * @deprecated 1.25.0 Use ERROR_CODES constant instead.
+     *
+     * @var array<string, string>
+     */
+    public static $errorCodes = self::ERROR_CODES;
+
+    /**
+     * @param mixed $value
+     */
+    public static function throwError($value): string
+    {
+        return in_array($value, self::ERROR_CODES, true) ? $value : self::ERROR_CODES['value'];
+    }
+
+    /**
+     * ERROR_TYPE.
+     *
+     * @param mixed $value Value to check
+     *
+     * @return array|int|string
+     */
+    public static function type($value = '')
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        $i = 1;
+        foreach (self::ERROR_CODES as $errorCode) {
+            if ($value === $errorCode) {
+                return $i;
+            }
+            ++$i;
+        }
+
+        return self::NA();
+    }
+
+    /**
+     * NULL.
+     *
+     * Returns the error value #NULL!
+     *
+     * @return string #NULL!
+     */
+    public static function null(): string
+    {
+        return self::ERROR_CODES['null'];
+    }
+
+    /**
+     * NaN.
+     *
+     * Returns the error value #NUM!
+     *
+     * @return string #NUM!
+     */
+    public static function NAN(): string
+    {
+        return self::ERROR_CODES['num'];
+    }
+
+    /**
+     * REF.
+     *
+     * Returns the error value #REF!
+     *
+     * @return string #REF!
+     */
+    public static function REF(): string
+    {
+        return self::ERROR_CODES['reference'];
+    }
+
+    /**
+     * NA.
+     *
+     * Excel Function:
+     *        =NA()
+     *
+     * Returns the error value #N/A
+     *        #N/A is the error value that means "no value is available."
+     *
+     * @return string #N/A!
+     */
+    public static function NA(): string
+    {
+        return self::ERROR_CODES['na'];
+    }
+
+    /**
+     * VALUE.
+     *
+     * Returns the error value #VALUE!
+     *
+     * @return string #VALUE!
+     */
+    public static function VALUE(): string
+    {
+        return self::ERROR_CODES['value'];
+    }
+
+    /**
+     * NAME.
+     *
+     * Returns the error value #NAME?
+     *
+     * @return string #NAME?
+     */
+    public static function NAME(): string
+    {
+        return self::ERROR_CODES['name'];
+    }
+
+    /**
+     * DIV0.
+     *
+     * @return string #DIV/0!
+     */
+    public static function DIV0(): string
+    {
+        return self::ERROR_CODES['divisionbyzero'];
+    }
+
+    /**
+     * CALC.
+     *
+     * @return string #CALC!
+     */
+    public static function CALC(): string
+    {
+        return self::ERROR_CODES['calculation'];
+    }
+}

+ 328 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/Value.php

@@ -0,0 +1,328 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Information;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\NamedRange;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class Value
+{
+    use ArrayEnabled;
+
+    /**
+     * IS_BLANK.
+     *
+     * @param mixed $value Value to check
+     *                      Or can be an array of values
+     *
+     * @return array|bool
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function isBlank($value = null)
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        return $value === null;
+    }
+
+    /**
+     * IS_REF.
+     *
+     * @param mixed $value Value to check
+     *
+     * @return bool
+     */
+    public static function isRef($value, ?Cell $cell = null)
+    {
+        if ($cell === null || $value === $cell->getCoordinate()) {
+            return false;
+        }
+
+        $cellValue = Functions::trimTrailingRange($value);
+        if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/ui', $cellValue) === 1) {
+            [$worksheet, $cellValue] = Worksheet::extractSheetTitle($cellValue, true);
+            if (!empty($worksheet) && $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheet) === null) {
+                return false;
+            }
+            [$column, $row] = Coordinate::indexesFromString($cellValue);
+            if ($column > 16384 || $row > 1048576) {
+                return false;
+            }
+
+            return true;
+        }
+
+        $namedRange = $cell->getWorksheet()->getParentOrThrow()->getNamedRange($value);
+
+        return $namedRange instanceof NamedRange;
+    }
+
+    /**
+     * IS_EVEN.
+     *
+     * @param mixed $value Value to check
+     *                      Or can be an array of values
+     *
+     * @return array|bool|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function isEven($value = null)
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        if ($value === null) {
+            return ExcelError::NAME();
+        } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
+            return ExcelError::VALUE();
+        }
+
+        return ((int) fmod($value, 2)) === 0;
+    }
+
+    /**
+     * IS_ODD.
+     *
+     * @param mixed $value Value to check
+     *                      Or can be an array of values
+     *
+     * @return array|bool|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function isOdd($value = null)
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        if ($value === null) {
+            return ExcelError::NAME();
+        } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
+            return ExcelError::VALUE();
+        }
+
+        return ((int) fmod($value, 2)) !== 0;
+    }
+
+    /**
+     * IS_NUMBER.
+     *
+     * @param mixed $value Value to check
+     *                      Or can be an array of values
+     *
+     * @return array|bool
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function isNumber($value = null)
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        if (is_string($value)) {
+            return false;
+        }
+
+        return is_numeric($value);
+    }
+
+    /**
+     * IS_LOGICAL.
+     *
+     * @param mixed $value Value to check
+     *                      Or can be an array of values
+     *
+     * @return array|bool
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function isLogical($value = null)
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        return is_bool($value);
+    }
+
+    /**
+     * IS_TEXT.
+     *
+     * @param mixed $value Value to check
+     *                      Or can be an array of values
+     *
+     * @return array|bool
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function isText($value = null)
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        return is_string($value) && !ErrorValue::isError($value);
+    }
+
+    /**
+     * IS_NONTEXT.
+     *
+     * @param mixed $value Value to check
+     *                      Or can be an array of values
+     *
+     * @return array|bool
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function isNonText($value = null)
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        return !self::isText($value);
+    }
+
+    /**
+     * ISFORMULA.
+     *
+     * @param mixed $cellReference The cell to check
+     * @param ?Cell $cell The current cell (containing this formula)
+     *
+     * @return array|bool|string
+     */
+    public static function isFormula($cellReference = '', ?Cell $cell = null)
+    {
+        if ($cell === null) {
+            return ExcelError::REF();
+        }
+
+        $fullCellReference = Functions::expandDefinedName((string) $cellReference, $cell);
+
+        if (strpos($cellReference, '!') !== false) {
+            $cellReference = Functions::trimSheetFromCellReference($cellReference);
+            $cellReferences = Coordinate::extractAllCellReferencesInRange($cellReference);
+            if (count($cellReferences) > 1) {
+                return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $cellReferences, $cell);
+            }
+        }
+
+        $fullCellReference = Functions::trimTrailingRange($fullCellReference);
+
+        preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $fullCellReference, $matches);
+
+        $fullCellReference = $matches[6] . $matches[7];
+        $worksheetName = str_replace("''", "'", trim($matches[2], "'"));
+
+        $worksheet = (!empty($worksheetName))
+            ? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheetName)
+            : $cell->getWorksheet();
+
+        return ($worksheet !== null) ? $worksheet->getCell($fullCellReference)->isFormula() : ExcelError::REF();
+    }
+
+    /**
+     * N.
+     *
+     * Returns a value converted to a number
+     *
+     * @param null|mixed $value The value you want converted
+     *
+     * @return number|string N converts values listed in the following table
+     *        If value is or refers to N returns
+     *        A number            That number value
+     *        A date              The Excel serialized number of that date
+     *        TRUE                1
+     *        FALSE               0
+     *        An error value      The error value
+     *        Anything else       0
+     */
+    public static function asNumber($value = null)
+    {
+        while (is_array($value)) {
+            $value = array_shift($value);
+        }
+
+        switch (gettype($value)) {
+            case 'double':
+            case 'float':
+            case 'integer':
+                return $value;
+            case 'boolean':
+                return (int) $value;
+            case 'string':
+                //    Errors
+                if ((strlen($value) > 0) && ($value[0] == '#')) {
+                    return $value;
+                }
+
+                break;
+        }
+
+        return 0;
+    }
+
+    /**
+     * TYPE.
+     *
+     * Returns a number that identifies the type of a value
+     *
+     * @param null|mixed $value The value you want tested
+     *
+     * @return number N converts values listed in the following table
+     *        If value is or refers to N returns
+     *        A number            1
+     *        Text                2
+     *        Logical Value       4
+     *        An error value      16
+     *        Array or Matrix     64
+     */
+    public static function type($value = null)
+    {
+        $value = Functions::flattenArrayIndexed($value);
+        if (is_array($value) && (count($value) > 1)) {
+            end($value);
+            $a = key($value);
+            //    Range of cells is an error
+            if (Functions::isCellValue($a)) {
+                return 16;
+            //    Test for Matrix
+            } elseif (Functions::isMatrixValue($a)) {
+                return 64;
+            }
+        } elseif (empty($value)) {
+            //    Empty Cell
+            return 1;
+        }
+
+        $value = Functions::flattenSingleValue($value);
+        if (($value === null) || (is_float($value)) || (is_int($value))) {
+            return 1;
+        } elseif (is_bool($value)) {
+            return 4;
+        } elseif (is_array($value)) {
+            return 64;
+        } elseif (is_string($value)) {
+            //    Errors
+            if ((strlen($value) > 0) && ($value[0] == '#')) {
+                return 16;
+            }
+
+            return 2;
+        }
+
+        return 0;
+    }
+}

+ 81 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
+
+class Filter
+{
+    /**
+     * @param mixed $lookupArray
+     * @param mixed $matchArray
+     * @param mixed $ifEmpty
+     *
+     * @return mixed
+     */
+    public static function filter($lookupArray, $matchArray, $ifEmpty = null)
+    {
+        if (!is_array($matchArray)) {
+            return ExcelError::VALUE();
+        }
+
+        $matchArray = self::enumerateArrayKeys($matchArray);
+
+        $result = (Matrix::isColumnVector($matchArray))
+            ? self::filterByRow($lookupArray, $matchArray)
+            : self::filterByColumn($lookupArray, $matchArray);
+
+        if (empty($result)) {
+            return $ifEmpty ?? ExcelError::CALC();
+        }
+
+        return array_values(array_map('array_values', $result));
+    }
+
+    private static function enumerateArrayKeys(array $sortArray): array
+    {
+        array_walk(
+            $sortArray,
+            function (&$columns): void {
+                if (is_array($columns)) {
+                    $columns = array_values($columns);
+                }
+            }
+        );
+
+        return array_values($sortArray);
+    }
+
+    private static function filterByRow(array $lookupArray, array $matchArray): array
+    {
+        $matchArray = array_values(array_column($matchArray, 0));
+
+        return array_filter(
+            array_values($lookupArray),
+            function ($index) use ($matchArray): bool {
+                return (bool) $matchArray[$index];
+            },
+            ARRAY_FILTER_USE_KEY
+        );
+    }
+
+    private static function filterByColumn(array $lookupArray, array $matchArray): array
+    {
+        $lookupArray = Matrix::transpose($lookupArray);
+
+        if (count($matchArray) === 1) {
+            $matchArray = array_pop($matchArray);
+        }
+
+        array_walk(
+            $matchArray,
+            function (&$value): void {
+                $value = [$value];
+            }
+        );
+
+        $result = self::filterByRow($lookupArray, $matchArray);
+
+        return Matrix::transpose($result);
+    }
+}

+ 342 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Sort.php

@@ -0,0 +1,342 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class Sort extends LookupRefValidations
+{
+    public const ORDER_ASCENDING = 1;
+    public const ORDER_DESCENDING = -1;
+
+    /**
+     * SORT
+     * The SORT function returns a sorted array of the elements in an array.
+     * The returned array is the same shape as the provided array argument.
+     * Both $sortIndex and $sortOrder can be arrays, to provide multi-level sorting.
+     *
+     * @param mixed $sortArray The range of cells being sorted
+     * @param mixed $sortIndex The column or row number within the sortArray to sort on
+     * @param mixed $sortOrder Flag indicating whether to sort ascending or descending
+     *                          Ascending = 1 (self::ORDER_ASCENDING)
+     *                          Descending = -1 (self::ORDER_DESCENDING)
+     * @param mixed $byColumn Whether the sort should be determined by row (the default) or by column
+     *
+     * @return mixed The sorted values from the sort range
+     */
+    public static function sort($sortArray, $sortIndex = 1, $sortOrder = self::ORDER_ASCENDING, $byColumn = false)
+    {
+        if (!is_array($sortArray)) {
+            // Scalars are always returned "as is"
+            return $sortArray;
+        }
+
+        $sortArray = self::enumerateArrayKeys($sortArray);
+
+        $byColumn = (bool) $byColumn;
+        $lookupIndexSize = $byColumn ? count($sortArray) : count($sortArray[0]);
+
+        try {
+            // If $sortIndex and $sortOrder are scalars, then convert them into arrays
+            if (is_scalar($sortIndex)) {
+                $sortIndex = [$sortIndex];
+                $sortOrder = is_scalar($sortOrder) ? [$sortOrder] : $sortOrder;
+            }
+            // but the values of those array arguments still need validation
+            $sortOrder = (empty($sortOrder) ? [self::ORDER_ASCENDING] : $sortOrder);
+            self::validateArrayArgumentsForSort($sortIndex, $sortOrder, $lookupIndexSize);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // We want a simple, enumrated array of arrays where we can reference column by its index number.
+        $sortArray = array_values(array_map('array_values', $sortArray));
+
+        return ($byColumn === true)
+            ? self::sortByColumn($sortArray, $sortIndex, $sortOrder)
+            : self::sortByRow($sortArray, $sortIndex, $sortOrder);
+    }
+
+    /**
+     * SORTBY
+     * The SORTBY function sorts the contents of a range or array based on the values in a corresponding range or array.
+     * The returned array is the same shape as the provided array argument.
+     * Both $sortIndex and $sortOrder can be arrays, to provide multi-level sorting.
+     *
+     * @param mixed $sortArray The range of cells being sorted
+     * @param mixed $args
+     *              At least one additional argument must be provided, The vector or range to sort on
+     *              After that, arguments are passed as pairs:
+     *                    sort order: ascending or descending
+     *                         Ascending = 1 (self::ORDER_ASCENDING)
+     *                         Descending = -1 (self::ORDER_DESCENDING)
+     *                    additional arrays or ranges for multi-level sorting
+     *
+     * @return mixed The sorted values from the sort range
+     */
+    public static function sortBy($sortArray, ...$args)
+    {
+        if (!is_array($sortArray)) {
+            // Scalars are always returned "as is"
+            return $sortArray;
+        }
+
+        $sortArray = self::enumerateArrayKeys($sortArray);
+
+        $lookupArraySize = count($sortArray);
+        $argumentCount = count($args);
+
+        try {
+            $sortBy = $sortOrder = [];
+            for ($i = 0; $i < $argumentCount; $i += 2) {
+                $sortBy[] = self::validateSortVector($args[$i], $lookupArraySize);
+                $sortOrder[] = self::validateSortOrder($args[$i + 1] ?? self::ORDER_ASCENDING);
+            }
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        return self::processSortBy($sortArray, $sortBy, $sortOrder);
+    }
+
+    private static function enumerateArrayKeys(array $sortArray): array
+    {
+        array_walk(
+            $sortArray,
+            function (&$columns): void {
+                if (is_array($columns)) {
+                    $columns = array_values($columns);
+                }
+            }
+        );
+
+        return array_values($sortArray);
+    }
+
+    /**
+     * @param mixed $sortIndex
+     * @param mixed $sortOrder
+     */
+    private static function validateScalarArgumentsForSort(&$sortIndex, &$sortOrder, int $sortArraySize): void
+    {
+        if (is_array($sortIndex) || is_array($sortOrder)) {
+            throw new Exception(ExcelError::VALUE());
+        }
+
+        $sortIndex = self::validatePositiveInt($sortIndex, false);
+
+        if ($sortIndex > $sortArraySize) {
+            throw new Exception(ExcelError::VALUE());
+        }
+
+        $sortOrder = self::validateSortOrder($sortOrder);
+    }
+
+    /**
+     * @param mixed $sortVector
+     */
+    private static function validateSortVector($sortVector, int $sortArraySize): array
+    {
+        if (!is_array($sortVector)) {
+            throw new Exception(ExcelError::VALUE());
+        }
+
+        // It doesn't matter if it's a row or a column vectors, it works either way
+        $sortVector = Functions::flattenArray($sortVector);
+        if (count($sortVector) !== $sortArraySize) {
+            throw new Exception(ExcelError::VALUE());
+        }
+
+        return $sortVector;
+    }
+
+    /**
+     * @param mixed $sortOrder
+     */
+    private static function validateSortOrder($sortOrder): int
+    {
+        $sortOrder = self::validateInt($sortOrder);
+        if (($sortOrder == self::ORDER_ASCENDING || $sortOrder === self::ORDER_DESCENDING) === false) {
+            throw new Exception(ExcelError::VALUE());
+        }
+
+        return $sortOrder;
+    }
+
+    /**
+     * @param array $sortIndex
+     * @param mixed $sortOrder
+     */
+    private static function validateArrayArgumentsForSort(&$sortIndex, &$sortOrder, int $sortArraySize): void
+    {
+        // It doesn't matter if they're row or column vectors, it works either way
+        $sortIndex = Functions::flattenArray($sortIndex);
+        $sortOrder = Functions::flattenArray($sortOrder);
+
+        if (
+            count($sortOrder) === 0 || count($sortOrder) > $sortArraySize ||
+            (count($sortOrder) > count($sortIndex))
+        ) {
+            throw new Exception(ExcelError::VALUE());
+        }
+
+        if (count($sortIndex) > count($sortOrder)) {
+            // If $sortOrder has fewer elements than $sortIndex, then the last order element is repeated.
+            $sortOrder = array_merge(
+                $sortOrder,
+                array_fill(0, count($sortIndex) - count($sortOrder), array_pop($sortOrder))
+            );
+        }
+
+        foreach ($sortIndex as $key => &$value) {
+            self::validateScalarArgumentsForSort($value, $sortOrder[$key], $sortArraySize);
+        }
+    }
+
+    private static function prepareSortVectorValues(array $sortVector): array
+    {
+        // Strings should be sorted case-insensitive; with booleans converted to locale-strings
+        return array_map(
+            function ($value) {
+                if (is_bool($value)) {
+                    return ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
+                } elseif (is_string($value)) {
+                    return StringHelper::strToLower($value);
+                }
+
+                return $value;
+            },
+            $sortVector
+        );
+    }
+
+    /**
+     * @param array[] $sortIndex
+     * @param int[] $sortOrder
+     */
+    private static function processSortBy(array $sortArray, array $sortIndex, $sortOrder): array
+    {
+        $sortArguments = [];
+        $sortData = [];
+        foreach ($sortIndex as $index => $sortValues) {
+            $sortData[] = $sortValues;
+            $sortArguments[] = self::prepareSortVectorValues($sortValues);
+            $sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC;
+        }
+        $sortArguments = self::applyPHP7Patch($sortArray, $sortArguments);
+
+        $sortVector = self::executeVectorSortQuery($sortData, $sortArguments);
+
+        return self::sortLookupArrayFromVector($sortArray, $sortVector);
+    }
+
+    /**
+     * @param int[] $sortIndex
+     * @param int[] $sortOrder
+     */
+    private static function sortByRow(array $sortArray, array $sortIndex, array $sortOrder): array
+    {
+        $sortVector = self::buildVectorForSort($sortArray, $sortIndex, $sortOrder);
+
+        return self::sortLookupArrayFromVector($sortArray, $sortVector);
+    }
+
+    /**
+     * @param int[] $sortIndex
+     * @param int[] $sortOrder
+     */
+    private static function sortByColumn(array $sortArray, array $sortIndex, array $sortOrder): array
+    {
+        $sortArray = Matrix::transpose($sortArray);
+        $result = self::sortByRow($sortArray, $sortIndex, $sortOrder);
+
+        return Matrix::transpose($result);
+    }
+
+    /**
+     * @param int[] $sortIndex
+     * @param int[] $sortOrder
+     */
+    private static function buildVectorForSort(array $sortArray, array $sortIndex, array $sortOrder): array
+    {
+        $sortArguments = [];
+        $sortData = [];
+        foreach ($sortIndex as $index => $sortIndexValue) {
+            $sortValues = array_column($sortArray, $sortIndexValue - 1);
+            $sortData[] = $sortValues;
+            $sortArguments[] = self::prepareSortVectorValues($sortValues);
+            $sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC;
+        }
+        $sortArguments = self::applyPHP7Patch($sortArray, $sortArguments);
+
+        $sortData = self::executeVectorSortQuery($sortData, $sortArguments);
+
+        return $sortData;
+    }
+
+    private static function executeVectorSortQuery(array $sortData, array $sortArguments): array
+    {
+        $sortData = Matrix::transpose($sortData);
+
+        // We need to set an index that can be retained, as array_multisort doesn't maintain numeric keys.
+        $sortDataIndexed = [];
+        foreach ($sortData as $key => $value) {
+            $sortDataIndexed[Coordinate::stringFromColumnIndex($key + 1)] = $value;
+        }
+        unset($sortData);
+
+        $sortArguments[] = &$sortDataIndexed;
+
+        array_multisort(...$sortArguments);
+
+        // After the sort, we restore the numeric keys that will now be in the correct, sorted order
+        $sortedData = [];
+        foreach (array_keys($sortDataIndexed) as $key) {
+            $sortedData[] = Coordinate::columnIndexFromString($key) - 1;
+        }
+
+        return $sortedData;
+    }
+
+    private static function sortLookupArrayFromVector(array $sortArray, array $sortVector): array
+    {
+        // Building a new array in the correct (sorted) order works; but may be memory heavy for larger arrays
+        $sortedArray = [];
+        foreach ($sortVector as $index) {
+            $sortedArray[] = $sortArray[$index];
+        }
+
+        return $sortedArray;
+
+//        uksort(
+//            $lookupArray,
+//            function (int $a, int $b) use (array $sortVector) {
+//                return $sortVector[$a] <=> $sortVector[$b];
+//            }
+//        );
+//
+//        return $lookupArray;
+    }
+
+    /**
+     * Hack to handle PHP 7:
+     * From PHP 8.0.0, If two members compare as equal in a sort, they retain their original order;
+     *      but prior to PHP 8.0.0, their relative order in the sorted array was undefined.
+     * MS Excel replicates the PHP 8.0.0 behaviour, retaining the original order of matching elements.
+     * To replicate that behaviour with PHP 7, we add an extra sort based on the row index.
+     */
+    private static function applyPHP7Patch(array $sortArray, array $sortArguments): array
+    {
+        if (PHP_VERSION_ID < 80000) {
+            $sortArguments[] = range(1, count($sortArray));
+            $sortArguments[] = SORT_ASC;
+        }
+
+        return $sortArguments;
+    }
+}

+ 141 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Unique.php

@@ -0,0 +1,141 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class Unique
+{
+    /**
+     * UNIQUE
+     * The UNIQUE function searches for value either from a one-row or one-column range or from an array.
+     *
+     * @param mixed $lookupVector The range of cells being searched
+     * @param mixed $byColumn Whether the uniqueness should be determined by row (the default) or by column
+     * @param mixed $exactlyOnce Whether the function should return only entries that occur just once in the list
+     *
+     * @return mixed The unique values from the search range
+     */
+    public static function unique($lookupVector, $byColumn = false, $exactlyOnce = false)
+    {
+        if (!is_array($lookupVector)) {
+            // Scalars are always returned "as is"
+            return $lookupVector;
+        }
+
+        $byColumn = (bool) $byColumn;
+        $exactlyOnce = (bool) $exactlyOnce;
+
+        return ($byColumn === true)
+            ? self::uniqueByColumn($lookupVector, $exactlyOnce)
+            : self::uniqueByRow($lookupVector, $exactlyOnce);
+    }
+
+    /**
+     * @return mixed
+     */
+    private static function uniqueByRow(array $lookupVector, bool $exactlyOnce)
+    {
+        // When not $byColumn, we count whole rows or values, not individual values
+        //      so implode each row into a single string value
+        array_walk(
+            $lookupVector,
+            function (array &$value): void {
+                $value = implode(chr(0x00), $value);
+            }
+        );
+
+        $result = self::countValuesCaseInsensitive($lookupVector);
+
+        if ($exactlyOnce === true) {
+            $result = self::exactlyOnceFilter($result);
+        }
+
+        if (count($result) === 0) {
+            return ExcelError::CALC();
+        }
+
+        $result = array_keys($result);
+
+        // restore rows from their strings
+        array_walk(
+            $result,
+            function (string &$value): void {
+                $value = explode(chr(0x00), $value);
+            }
+        );
+
+        return (count($result) === 1) ? array_pop($result) : $result;
+    }
+
+    /**
+     * @return mixed
+     */
+    private static function uniqueByColumn(array $lookupVector, bool $exactlyOnce)
+    {
+        $flattenedLookupVector = Functions::flattenArray($lookupVector);
+
+        if (count($lookupVector, COUNT_RECURSIVE) > count($flattenedLookupVector, COUNT_RECURSIVE) + 1) {
+            // We're looking at a full column check (multiple rows)
+            $transpose = Matrix::transpose($lookupVector);
+            $result = self::uniqueByRow($transpose, $exactlyOnce);
+
+            return (is_array($result)) ? Matrix::transpose($result) : $result;
+        }
+
+        $result = self::countValuesCaseInsensitive($flattenedLookupVector);
+
+        if ($exactlyOnce === true) {
+            $result = self::exactlyOnceFilter($result);
+        }
+
+        if (count($result) === 0) {
+            return ExcelError::CALC();
+        }
+
+        $result = array_keys($result);
+
+        return $result;
+    }
+
+    private static function countValuesCaseInsensitive(array $caseSensitiveLookupValues): array
+    {
+        $caseInsensitiveCounts = array_count_values(
+            array_map(
+                function (string $value) {
+                    return StringHelper::strToUpper($value);
+                },
+                $caseSensitiveLookupValues
+            )
+        );
+
+        $caseSensitiveCounts = [];
+        foreach ($caseInsensitiveCounts as $caseInsensitiveKey => $count) {
+            if (is_numeric($caseInsensitiveKey)) {
+                $caseSensitiveCounts[$caseInsensitiveKey] = $count;
+            } else {
+                foreach ($caseSensitiveLookupValues as $caseSensitiveValue) {
+                    if ($caseInsensitiveKey === StringHelper::strToUpper($caseSensitiveValue)) {
+                        $caseSensitiveCounts[$caseSensitiveValue] = $count;
+
+                        break;
+                    }
+                }
+            }
+        }
+
+        return $caseSensitiveCounts;
+    }
+
+    private static function exactlyOnceFilter(array $values): array
+    {
+        return array_filter(
+            $values,
+            function ($value) {
+                return $value === 1;
+            }
+        );
+    }
+}

+ 24 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressRange.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+interface AddressRange
+{
+    public const MAX_ROW = 1048576;
+
+    public const MAX_COLUMN = 'XFD';
+
+    public const MAX_COLUMN_INT = 16384;
+
+    /**
+     * @return mixed
+     */
+    public function from();
+
+    /**
+     * @return mixed
+     */
+    public function to();
+
+    public function __toString(): string;
+}

+ 166 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/CellAddress.php

@@ -0,0 +1,166 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class CellAddress
+{
+    /**
+     * @var ?Worksheet
+     */
+    protected $worksheet;
+
+    /**
+     * @var string
+     */
+    protected $cellAddress;
+
+    /**
+     * @var string
+     */
+    protected $columnName;
+
+    /**
+     * @var int
+     */
+    protected $columnId;
+
+    /**
+     * @var int
+     */
+    protected $rowId;
+
+    public function __construct(string $cellAddress, ?Worksheet $worksheet = null)
+    {
+        $this->cellAddress = str_replace('$', '', $cellAddress);
+        [$this->columnId, $this->rowId, $this->columnName] = Coordinate::indexesFromString($this->cellAddress);
+        $this->worksheet = $worksheet;
+    }
+
+    /**
+     * @param mixed $columnId
+     * @param mixed $rowId
+     */
+    private static function validateColumnAndRow($columnId, $rowId): void
+    {
+        if (!is_numeric($columnId) || $columnId <= 0 || !is_numeric($rowId) || $rowId <= 0) {
+            throw new Exception('Row and Column Ids must be positive integer values');
+        }
+    }
+
+    /**
+     * @param mixed $columnId
+     * @param mixed $rowId
+     */
+    public static function fromColumnAndRow($columnId, $rowId, ?Worksheet $worksheet = null): self
+    {
+        self::validateColumnAndRow($columnId, $rowId);
+
+        /** @phpstan-ignore-next-line */
+        return new static(Coordinate::stringFromColumnIndex($columnId) . ((string) $rowId), $worksheet);
+    }
+
+    public static function fromColumnRowArray(array $array, ?Worksheet $worksheet = null): self
+    {
+        [$columnId, $rowId] = $array;
+
+        return static::fromColumnAndRow($columnId, $rowId, $worksheet);
+    }
+
+    /**
+     * @param mixed $cellAddress
+     */
+    public static function fromCellAddress($cellAddress, ?Worksheet $worksheet = null): self
+    {
+        /** @phpstan-ignore-next-line */
+        return new static($cellAddress, $worksheet);
+    }
+
+    /**
+     * The returned address string will contain the worksheet name as well, if available,
+     *     (ie. if a Worksheet was provided to the constructor).
+     *     e.g. "'Mark''s Worksheet'!C5".
+     */
+    public function fullCellAddress(): string
+    {
+        if ($this->worksheet !== null) {
+            $title = str_replace("'", "''", $this->worksheet->getTitle());
+
+            return "'{$title}'!{$this->cellAddress}";
+        }
+
+        return $this->cellAddress;
+    }
+
+    public function worksheet(): ?Worksheet
+    {
+        return $this->worksheet;
+    }
+
+    /**
+     * The returned address string will contain just the column/row address,
+     *     (even if a Worksheet was provided to the constructor).
+     *     e.g. "C5".
+     */
+    public function cellAddress(): string
+    {
+        return $this->cellAddress;
+    }
+
+    public function rowId(): int
+    {
+        return $this->rowId;
+    }
+
+    public function columnId(): int
+    {
+        return $this->columnId;
+    }
+
+    public function columnName(): string
+    {
+        return $this->columnName;
+    }
+
+    public function nextRow(int $offset = 1): self
+    {
+        $newRowId = $this->rowId + $offset;
+        if ($newRowId < 1) {
+            $newRowId = 1;
+        }
+
+        return static::fromColumnAndRow($this->columnId, $newRowId);
+    }
+
+    public function previousRow(int $offset = 1): self
+    {
+        return $this->nextRow(0 - $offset);
+    }
+
+    public function nextColumn(int $offset = 1): self
+    {
+        $newColumnId = $this->columnId + $offset;
+        if ($newColumnId < 1) {
+            $newColumnId = 1;
+        }
+
+        return static::fromColumnAndRow($newColumnId, $this->rowId);
+    }
+
+    public function previousColumn(int $offset = 1): self
+    {
+        return $this->nextColumn(0 - $offset);
+    }
+
+    /**
+     * The returned address string will contain the worksheet name as well, if available,
+     *     (ie. if a Worksheet was provided to the constructor).
+     *     e.g. "'Mark''s Worksheet'!C5".
+     */
+    public function __toString()
+    {
+        return $this->fullCellAddress();
+    }
+}

+ 136 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/CellRange.php

@@ -0,0 +1,136 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class CellRange implements AddressRange
+{
+    /**
+     * @var CellAddress
+     */
+    protected $from;
+
+    /**
+     * @var CellAddress
+     */
+    protected $to;
+
+    public function __construct(CellAddress $from, CellAddress $to)
+    {
+        $this->validateFromTo($from, $to);
+    }
+
+    private function validateFromTo(CellAddress $from, CellAddress $to): void
+    {
+        // Identify actual top-left and bottom-right values (in case we've been given top-right and bottom-left)
+        $firstColumn = min($from->columnId(), $to->columnId());
+        $firstRow = min($from->rowId(), $to->rowId());
+        $lastColumn = max($from->columnId(), $to->columnId());
+        $lastRow = max($from->rowId(), $to->rowId());
+
+        $fromWorksheet = $from->worksheet();
+        $toWorksheet = $to->worksheet();
+        $this->validateWorksheets($fromWorksheet, $toWorksheet);
+
+        $this->from = $this->cellAddressWrapper($firstColumn, $firstRow, $fromWorksheet);
+        $this->to = $this->cellAddressWrapper($lastColumn, $lastRow, $toWorksheet);
+    }
+
+    private function validateWorksheets(?Worksheet $fromWorksheet, ?Worksheet $toWorksheet): void
+    {
+        if ($fromWorksheet !== null && $toWorksheet !== null) {
+            // We could simply compare worksheets rather than worksheet titles; but at some point we may introduce
+            //    support for 3d ranges; and at that point we drop this check and let the validation fall through
+            //    to the check for same workbook; but unless we check on titles, this test will also detect if the
+            //    worksheets are in different spreadsheets, and the next check will never execute or throw its
+            //    own exception.
+            if ($fromWorksheet->getTitle() !== $toWorksheet->getTitle()) {
+                throw new Exception('3d Cell Ranges are not supported');
+            } elseif ($fromWorksheet->getParent() !== $toWorksheet->getParent()) {
+                throw new Exception('Worksheets must be in the same spreadsheet');
+            }
+        }
+    }
+
+    private function cellAddressWrapper(int $column, int $row, ?Worksheet $worksheet = null): CellAddress
+    {
+        $cellAddress = Coordinate::stringFromColumnIndex($column) . (string) $row;
+
+        return new class ($cellAddress, $worksheet) extends CellAddress {
+            public function nextRow(int $offset = 1): CellAddress
+            {
+                /** @var CellAddress $result */
+                $result = parent::nextRow($offset);
+                $this->rowId = $result->rowId;
+                $this->cellAddress = $result->cellAddress;
+
+                return $this;
+            }
+
+            public function previousRow(int $offset = 1): CellAddress
+            {
+                /** @var CellAddress $result */
+                $result = parent::previousRow($offset);
+                $this->rowId = $result->rowId;
+                $this->cellAddress = $result->cellAddress;
+
+                return $this;
+            }
+
+            public function nextColumn(int $offset = 1): CellAddress
+            {
+                /** @var CellAddress $result */
+                $result = parent::nextColumn($offset);
+                $this->columnId = $result->columnId;
+                $this->columnName = $result->columnName;
+                $this->cellAddress = $result->cellAddress;
+
+                return $this;
+            }
+
+            public function previousColumn(int $offset = 1): CellAddress
+            {
+                /** @var CellAddress $result */
+                $result = parent::previousColumn($offset);
+                $this->columnId = $result->columnId;
+                $this->columnName = $result->columnName;
+                $this->cellAddress = $result->cellAddress;
+
+                return $this;
+            }
+        };
+    }
+
+    public function from(): CellAddress
+    {
+        // Re-order from/to in case the cell addresses have been modified
+        $this->validateFromTo($this->from, $this->to);
+
+        return $this->from;
+    }
+
+    public function to(): CellAddress
+    {
+        // Re-order from/to in case the cell addresses have been modified
+        $this->validateFromTo($this->from, $this->to);
+
+        return $this->to;
+    }
+
+    public function __toString(): string
+    {
+        // Re-order from/to in case the cell addresses have been modified
+        $this->validateFromTo($this->from, $this->to);
+
+        if ($this->from->cellAddress() === $this->to->cellAddress()) {
+            return "{$this->from->fullCellAddress()}";
+        }
+
+        $fromAddress = $this->from->fullCellAddress();
+        $toAddress = $this->to->cellAddress();
+
+        return "{$fromAddress}:{$toAddress}";
+    }
+}

+ 125 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/ColumnRange.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class ColumnRange implements AddressRange
+{
+    /**
+     * @var ?Worksheet
+     */
+    protected $worksheet;
+
+    /**
+     * @var int
+     */
+    protected $from;
+
+    /**
+     * @var int
+     */
+    protected $to;
+
+    public function __construct(string $from, ?string $to = null, ?Worksheet $worksheet = null)
+    {
+        $this->validateFromTo(
+            Coordinate::columnIndexFromString($from),
+            Coordinate::columnIndexFromString($to ?? $from)
+        );
+        $this->worksheet = $worksheet;
+    }
+
+    public static function fromColumnIndexes(int $from, int $to, ?Worksheet $worksheet = null): self
+    {
+        return new self(Coordinate::stringFromColumnIndex($from), Coordinate::stringFromColumnIndex($to), $worksheet);
+    }
+
+    /**
+     * @param array<int|string> $array
+     */
+    public static function fromArray(array $array, ?Worksheet $worksheet = null): self
+    {
+        array_walk(
+            $array,
+            function (&$column): void {
+                $column = is_numeric($column) ? Coordinate::stringFromColumnIndex((int) $column) : $column;
+            }
+        );
+        /** @var string $from */
+        /** @var string $to */
+        [$from, $to] = $array;
+
+        return new self($from, $to, $worksheet);
+    }
+
+    private function validateFromTo(int $from, int $to): void
+    {
+        // Identify actual top and bottom values (in case we've been given bottom and top)
+        $this->from = min($from, $to);
+        $this->to = max($from, $to);
+    }
+
+    public function columnCount(): int
+    {
+        return $this->to - $this->from + 1;
+    }
+
+    public function shiftDown(int $offset = 1): self
+    {
+        $newFrom = $this->from + $offset;
+        $newFrom = ($newFrom < 1) ? 1 : $newFrom;
+
+        $newTo = $this->to + $offset;
+        $newTo = ($newTo < 1) ? 1 : $newTo;
+
+        return self::fromColumnIndexes($newFrom, $newTo, $this->worksheet);
+    }
+
+    public function shiftUp(int $offset = 1): self
+    {
+        return $this->shiftDown(0 - $offset);
+    }
+
+    public function from(): string
+    {
+        return Coordinate::stringFromColumnIndex($this->from);
+    }
+
+    public function to(): string
+    {
+        return Coordinate::stringFromColumnIndex($this->to);
+    }
+
+    public function fromIndex(): int
+    {
+        return $this->from;
+    }
+
+    public function toIndex(): int
+    {
+        return $this->to;
+    }
+
+    public function toCellRange(): CellRange
+    {
+        return new CellRange(
+            CellAddress::fromColumnAndRow($this->from, 1, $this->worksheet),
+            CellAddress::fromColumnAndRow($this->to, AddressRange::MAX_ROW)
+        );
+    }
+
+    public function __toString(): string
+    {
+        $from = $this->from();
+        $to = $this->to();
+
+        if ($this->worksheet !== null) {
+            $title = str_replace("'", "''", $this->worksheet->getTitle());
+
+            return "'{$title}'!{$from}:{$to}";
+        }
+
+        return "{$from}:{$to}";
+    }
+}

+ 66 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+class IgnoredErrors
+{
+    /** @var bool */
+    private $numberStoredAsText = false;
+
+    /** @var bool */
+    private $formula = false;
+
+    /** @var bool */
+    private $twoDigitTextYear = false;
+
+    /** @var bool */
+    private $evalError = false;
+
+    public function setNumberStoredAsText(bool $value): self
+    {
+        $this->numberStoredAsText = $value;
+
+        return $this;
+    }
+
+    public function getNumberStoredAsText(): bool
+    {
+        return $this->numberStoredAsText;
+    }
+
+    public function setFormula(bool $value): self
+    {
+        $this->formula = $value;
+
+        return $this;
+    }
+
+    public function getFormula(): bool
+    {
+        return $this->formula;
+    }
+
+    public function setTwoDigitTextYear(bool $value): self
+    {
+        $this->twoDigitTextYear = $value;
+
+        return $this;
+    }
+
+    public function getTwoDigitTextYear(): bool
+    {
+        return $this->twoDigitTextYear;
+    }
+
+    public function setEvalError(bool $value): self
+    {
+        $this->evalError = $value;
+
+        return $this;
+    }
+
+    public function getEvalError(): bool
+    {
+        return $this->evalError;
+    }
+}

+ 93 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/RowRange.php

@@ -0,0 +1,93 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class RowRange implements AddressRange
+{
+    /**
+     * @var ?Worksheet
+     */
+    protected $worksheet;
+
+    /**
+     * @var int
+     */
+    protected $from;
+
+    /**
+     * @var int
+     */
+    protected $to;
+
+    public function __construct(int $from, ?int $to = null, ?Worksheet $worksheet = null)
+    {
+        $this->validateFromTo($from, $to ?? $from);
+        $this->worksheet = $worksheet;
+    }
+
+    public static function fromArray(array $array, ?Worksheet $worksheet = null): self
+    {
+        [$from, $to] = $array;
+
+        return new self($from, $to, $worksheet);
+    }
+
+    private function validateFromTo(int $from, int $to): void
+    {
+        // Identify actual top and bottom values (in case we've been given bottom and top)
+        $this->from = min($from, $to);
+        $this->to = max($from, $to);
+    }
+
+    public function from(): int
+    {
+        return $this->from;
+    }
+
+    public function to(): int
+    {
+        return $this->to;
+    }
+
+    public function rowCount(): int
+    {
+        return $this->to - $this->from + 1;
+    }
+
+    public function shiftRight(int $offset = 1): self
+    {
+        $newFrom = $this->from + $offset;
+        $newFrom = ($newFrom < 1) ? 1 : $newFrom;
+
+        $newTo = $this->to + $offset;
+        $newTo = ($newTo < 1) ? 1 : $newTo;
+
+        return new self($newFrom, $newTo, $this->worksheet);
+    }
+
+    public function shiftLeft(int $offset = 1): self
+    {
+        return $this->shiftRight(0 - $offset);
+    }
+
+    public function toCellRange(): CellRange
+    {
+        return new CellRange(
+            CellAddress::fromColumnAndRow(Coordinate::columnIndexFromString('A'), $this->from, $this->worksheet),
+            CellAddress::fromColumnAndRow(Coordinate::columnIndexFromString(AddressRange::MAX_COLUMN), $this->to)
+        );
+    }
+
+    public function __toString(): string
+    {
+        if ($this->worksheet !== null) {
+            $title = str_replace("'", "''", $this->worksheet->getTitle());
+
+            return "'{$title}'!{$this->from}:{$this->to}";
+        }
+
+        return "{$this->from}:{$this->to}";
+    }
+}

+ 131 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php

@@ -0,0 +1,131 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+
+class CellReferenceHelper
+{
+    /**
+     * @var string
+     */
+    protected $beforeCellAddress;
+
+    /**
+     * @var int
+     */
+    protected $beforeColumn;
+
+    /**
+     * @var int
+     */
+    protected $beforeRow;
+
+    /**
+     * @var int
+     */
+    protected $numberOfColumns;
+
+    /**
+     * @var int
+     */
+    protected $numberOfRows;
+
+    public function __construct(string $beforeCellAddress = 'A1', int $numberOfColumns = 0, int $numberOfRows = 0)
+    {
+        $this->beforeCellAddress = str_replace('$', '', $beforeCellAddress);
+        $this->numberOfColumns = $numberOfColumns;
+        $this->numberOfRows = $numberOfRows;
+
+        // Get coordinate of $beforeCellAddress
+        [$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($beforeCellAddress);
+        $this->beforeColumn = (int) Coordinate::columnIndexFromString($beforeColumn);
+        $this->beforeRow = (int) $beforeRow;
+    }
+
+    public function beforeCellAddress(): string
+    {
+        return $this->beforeCellAddress;
+    }
+
+    public function refreshRequired(string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): bool
+    {
+        return $this->beforeCellAddress !== $beforeCellAddress ||
+            $this->numberOfColumns !== $numberOfColumns ||
+            $this->numberOfRows !== $numberOfRows;
+    }
+
+    public function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false): string
+    {
+        if (Coordinate::coordinateIsRange($cellReference)) {
+            throw new Exception('Only single cell references may be passed to this method.');
+        }
+
+        // Get coordinate of $cellReference
+        [$newColumn, $newRow] = Coordinate::coordinateFromString($cellReference);
+        $newColumnIndex = (int) Coordinate::columnIndexFromString(str_replace('$', '', $newColumn));
+        $newRowIndex = (int) str_replace('$', '', $newRow);
+
+        $absoluteColumn = $newColumn[0] === '$' ? '$' : '';
+        $absoluteRow = $newRow[0] === '$' ? '$' : '';
+        // Verify which parts should be updated
+        if ($includeAbsoluteReferences === false) {
+            $updateColumn = (($absoluteColumn !== '$') && $newColumnIndex >= $this->beforeColumn);
+            $updateRow = (($absoluteRow !== '$') && $newRowIndex >= $this->beforeRow);
+        } else {
+            $updateColumn = ($newColumnIndex >= $this->beforeColumn);
+            $updateRow = ($newRowIndex >= $this->beforeRow);
+        }
+
+        // Create new column reference
+        if ($updateColumn) {
+            $newColumn = $this->updateColumnReference($newColumnIndex, $absoluteColumn);
+        }
+
+        // Create new row reference
+        if ($updateRow) {
+            $newRow = $this->updateRowReference($newRowIndex, $absoluteRow);
+        }
+
+        // Return new reference
+        return "{$newColumn}{$newRow}";
+    }
+
+    public function cellAddressInDeleteRange(string $cellAddress): bool
+    {
+        [$cellColumn, $cellRow] = Coordinate::coordinateFromString($cellAddress);
+        $cellColumnIndex = Coordinate::columnIndexFromString($cellColumn);
+        //    Is cell within the range of rows/columns if we're deleting
+        if (
+            $this->numberOfRows < 0 &&
+            ($cellRow >= ($this->beforeRow + $this->numberOfRows)) &&
+            ($cellRow < $this->beforeRow)
+        ) {
+            return true;
+        } elseif (
+            $this->numberOfColumns < 0 &&
+            ($cellColumnIndex >= ($this->beforeColumn + $this->numberOfColumns)) &&
+            ($cellColumnIndex < $this->beforeColumn)
+        ) {
+            return true;
+        }
+
+        return false;
+    }
+
+    protected function updateColumnReference(int $newColumnIndex, string $absoluteColumn): string
+    {
+        $newColumn = Coordinate::stringFromColumnIndex(min($newColumnIndex + $this->numberOfColumns, AddressRange::MAX_COLUMN_INT));
+
+        return "{$absoluteColumn}{$newColumn}";
+    }
+
+    protected function updateRowReference(int $newRowIndex, string $absoluteRow): string
+    {
+        $newRow = $newRowIndex + $this->numberOfRows;
+        $newRow = ($newRow > AddressRange::MAX_ROW) ? AddressRange::MAX_ROW : $newRow;
+
+        return "{$absoluteRow}{$newRow}";
+    }
+}

+ 56 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+use PhpOffice\PhpSpreadsheet\Style\Font;
+
+class AxisText extends Properties
+{
+    /** @var ?int */
+    private $rotation;
+
+    /** @var Font */
+    private $font;
+
+    public function __construct()
+    {
+        parent::__construct();
+        $this->font = new Font();
+        $this->font->setSize(null, true);
+    }
+
+    public function setRotation(?int $rotation): self
+    {
+        $this->rotation = $rotation;
+
+        return $this;
+    }
+
+    public function getRotation(): ?int
+    {
+        return $this->rotation;
+    }
+
+    public function getFillColorObject(): ChartColor
+    {
+        $fillColor = $this->font->getChartColor();
+        if ($fillColor === null) {
+            $fillColor = new ChartColor();
+            $this->font->setChartColorFromObject($fillColor);
+        }
+
+        return $fillColor;
+    }
+
+    public function getFont(): Font
+    {
+        return $this->font;
+    }
+
+    public function setFont(Font $font): self
+    {
+        $this->font = $font;
+
+        return $this;
+    }
+}

+ 177 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/ChartColor.php

@@ -0,0 +1,177 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+class ChartColor
+{
+    const EXCEL_COLOR_TYPE_STANDARD = 'prstClr';
+    const EXCEL_COLOR_TYPE_SCHEME = 'schemeClr';
+    const EXCEL_COLOR_TYPE_RGB = 'srgbClr';
+    /** @deprecated 1.24 use EXCEL_COLOR_TYPE_RGB instead */
+    const EXCEL_COLOR_TYPE_ARGB = 'srgbClr';
+    const EXCEL_COLOR_TYPES = [
+        self::EXCEL_COLOR_TYPE_ARGB,
+        self::EXCEL_COLOR_TYPE_SCHEME,
+        self::EXCEL_COLOR_TYPE_STANDARD,
+    ];
+
+    /** @var string */
+    private $value = '';
+
+    /** @var string */
+    private $type = '';
+
+    /** @var ?int */
+    private $alpha;
+
+    /** @var ?int */
+    private $brightness;
+
+    /**
+     * @param string|string[] $value
+     */
+    public function __construct($value = '', ?int $alpha = null, ?string $type = null, ?int $brightness = null)
+    {
+        if (is_array($value)) {
+            $this->setColorPropertiesArray($value);
+        } else {
+            $this->setColorProperties($value, $alpha, $type, $brightness);
+        }
+    }
+
+    public function getValue(): string
+    {
+        return $this->value;
+    }
+
+    public function setValue(string $value): self
+    {
+        $this->value = $value;
+
+        return $this;
+    }
+
+    public function getType(): string
+    {
+        return $this->type;
+    }
+
+    public function setType(string $type): self
+    {
+        $this->type = $type;
+
+        return $this;
+    }
+
+    public function getAlpha(): ?int
+    {
+        return $this->alpha;
+    }
+
+    public function setAlpha(?int $alpha): self
+    {
+        $this->alpha = $alpha;
+
+        return $this;
+    }
+
+    public function getBrightness(): ?int
+    {
+        return $this->brightness;
+    }
+
+    public function setBrightness(?int $brightness): self
+    {
+        $this->brightness = $brightness;
+
+        return $this;
+    }
+
+    /**
+     * @param null|float|int|string $alpha
+     * @param null|float|int|string $brightness
+     */
+    public function setColorProperties(?string $color, $alpha = null, ?string $type = null, $brightness = null): self
+    {
+        if (empty($type) && !empty($color)) {
+            if (substr($color, 0, 1) === '*') {
+                $type = 'schemeClr';
+                $color = substr($color, 1);
+            } elseif (substr($color, 0, 1) === '/') {
+                $type = 'prstClr';
+                $color = substr($color, 1);
+            } elseif (preg_match('/^[0-9A-Fa-f]{6}$/', $color) === 1) {
+                $type = 'srgbClr';
+            }
+        }
+        if ($color !== null) {
+            $this->setValue("$color");
+        }
+        if ($type !== null) {
+            $this->setType($type);
+        }
+        if ($alpha === null) {
+            $this->setAlpha(null);
+        } elseif (is_numeric($alpha)) {
+            $this->setAlpha((int) $alpha);
+        }
+        if ($brightness === null) {
+            $this->setBrightness(null);
+        } elseif (is_numeric($brightness)) {
+            $this->setBrightness((int) $brightness);
+        }
+
+        return $this;
+    }
+
+    public function setColorPropertiesArray(array $color): self
+    {
+        return $this->setColorProperties(
+            $color['value'] ?? '',
+            $color['alpha'] ?? null,
+            $color['type'] ?? null,
+            $color['brightness'] ?? null
+        );
+    }
+
+    public function isUsable(): bool
+    {
+        return $this->type !== '' && $this->value !== '';
+    }
+
+    /**
+     * Get Color Property.
+     *
+     * @param string $propertyName
+     *
+     * @return null|int|string
+     */
+    public function getColorProperty($propertyName)
+    {
+        $retVal = null;
+        if ($propertyName === 'value') {
+            $retVal = $this->value;
+        } elseif ($propertyName === 'type') {
+            $retVal = $this->type;
+        } elseif ($propertyName === 'alpha') {
+            $retVal = $this->alpha;
+        } elseif ($propertyName === 'brightness') {
+            $retVal = $this->brightness;
+        }
+
+        return $retVal;
+    }
+
+    public static function alphaToXml(int $alpha): string
+    {
+        return (string) (100 - $alpha) . '000';
+    }
+
+    /**
+     * @param float|int|string $alpha
+     */
+    public static function alphaFromXml($alpha): int
+    {
+        return 100 - ((int) $alpha / 1000);
+    }
+}

+ 873 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php

@@ -0,0 +1,873 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
+
+use AccBarPlot;
+use AccLinePlot;
+use BarPlot;
+use ContourPlot;
+use Graph;
+use GroupBarPlot;
+use LinePlot;
+use PhpOffice\PhpSpreadsheet\Chart\Chart;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PieGraph;
+use PiePlot;
+use PiePlot3D;
+use PiePlotC;
+use RadarGraph;
+use RadarPlot;
+use ScatterPlot;
+use Spline;
+use StockPlot;
+
+/**
+ * Base class for different Jpgraph implementations as charts renderer.
+ */
+abstract class JpGraphRendererBase implements IRenderer
+{
+    private static $width = 640;
+
+    private static $height = 480;
+
+    private static $colourSet = [
+        'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1',
+        'darkmagenta', 'coral', 'dodgerblue3', 'eggplant',
+        'mediumblue', 'magenta', 'sandybrown', 'cyan',
+        'firebrick1', 'forestgreen', 'deeppink4', 'darkolivegreen',
+        'goldenrod2',
+    ];
+
+    private static $markSet;
+
+    private $chart;
+
+    private $graph;
+
+    private static $plotColour = 0;
+
+    private static $plotMark = 0;
+
+    /**
+     * Create a new jpgraph.
+     */
+    public function __construct(Chart $chart)
+    {
+        static::init();
+        $this->graph = null;
+        $this->chart = $chart;
+
+        self::$markSet = [
+            'diamond' => MARK_DIAMOND,
+            'square' => MARK_SQUARE,
+            'triangle' => MARK_UTRIANGLE,
+            'x' => MARK_X,
+            'star' => MARK_STAR,
+            'dot' => MARK_FILLEDCIRCLE,
+            'dash' => MARK_DTRIANGLE,
+            'circle' => MARK_CIRCLE,
+            'plus' => MARK_CROSS,
+        ];
+    }
+
+    /**
+     * This method should be overriden in descendants to do real JpGraph library initialization.
+     */
+    abstract protected static function init(): void;
+
+    private function formatPointMarker($seriesPlot, $markerID)
+    {
+        $plotMarkKeys = array_keys(self::$markSet);
+        if ($markerID === null) {
+            //    Use default plot marker (next marker in the series)
+            self::$plotMark %= count(self::$markSet);
+            $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
+        } elseif ($markerID !== 'none') {
+            //    Use specified plot marker (if it exists)
+            if (isset(self::$markSet[$markerID])) {
+                $seriesPlot->mark->SetType(self::$markSet[$markerID]);
+            } else {
+                //    If the specified plot marker doesn't exist, use default plot marker (next marker in the series)
+                self::$plotMark %= count(self::$markSet);
+                $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
+            }
+        } else {
+            //    Hide plot marker
+            $seriesPlot->mark->Hide();
+        }
+        $seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]);
+        $seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]);
+        $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
+
+        return $seriesPlot;
+    }
+
+    private function formatDataSetLabels($groupID, $datasetLabels, $rotation = '')
+    {
+        $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode() ?? '';
+        //    Retrieve any label formatting code
+        $datasetLabelFormatCode = stripslashes($datasetLabelFormatCode);
+
+        $testCurrentIndex = 0;
+        foreach ($datasetLabels as $i => $datasetLabel) {
+            if (is_array($datasetLabel)) {
+                if ($rotation == 'bar') {
+                    $datasetLabels[$i] = implode(' ', $datasetLabel);
+                } else {
+                    $datasetLabel = array_reverse($datasetLabel);
+                    $datasetLabels[$i] = implode("\n", $datasetLabel);
+                }
+            } else {
+                //    Format labels according to any formatting code
+                if ($datasetLabelFormatCode !== null) {
+                    $datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode);
+                }
+            }
+            ++$testCurrentIndex;
+        }
+
+        return $datasetLabels;
+    }
+
+    private function percentageSumCalculation($groupID, $seriesCount)
+    {
+        $sumValues = [];
+        //    Adjust our values to a percentage value across all series in the group
+        for ($i = 0; $i < $seriesCount; ++$i) {
+            if ($i == 0) {
+                $sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+            } else {
+                $nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+                foreach ($nextValues as $k => $value) {
+                    if (isset($sumValues[$k])) {
+                        $sumValues[$k] += $value;
+                    } else {
+                        $sumValues[$k] = $value;
+                    }
+                }
+            }
+        }
+
+        return $sumValues;
+    }
+
+    private function percentageAdjustValues($dataValues, $sumValues)
+    {
+        foreach ($dataValues as $k => $dataValue) {
+            $dataValues[$k] = $dataValue / $sumValues[$k] * 100;
+        }
+
+        return $dataValues;
+    }
+
+    private function getCaption($captionElement)
+    {
+        //    Read any caption
+        $caption = ($captionElement !== null) ? $captionElement->getCaption() : null;
+        //    Test if we have a title caption to display
+        if ($caption !== null) {
+            //    If we do, it could be a plain string or an array
+            if (is_array($caption)) {
+                //    Implode an array to a plain string
+                $caption = implode('', $caption);
+            }
+        }
+
+        return $caption;
+    }
+
+    private function renderTitle(): void
+    {
+        $title = $this->getCaption($this->chart->getTitle());
+        if ($title !== null) {
+            $this->graph->title->Set($title);
+        }
+    }
+
+    private function renderLegend(): void
+    {
+        $legend = $this->chart->getLegend();
+        if ($legend !== null) {
+            $legendPosition = $legend->getPosition();
+            switch ($legendPosition) {
+                case 'r':
+                    $this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); //    right
+                    $this->graph->legend->SetColumns(1);
+
+                    break;
+                case 'l':
+                    $this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); //    left
+                    $this->graph->legend->SetColumns(1);
+
+                    break;
+                case 't':
+                    $this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); //    top
+
+                    break;
+                case 'b':
+                    $this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); //    bottom
+
+                    break;
+                default:
+                    $this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); //    top-right
+                    $this->graph->legend->SetColumns(1);
+
+                    break;
+            }
+        } else {
+            $this->graph->legend->Hide();
+        }
+    }
+
+    private function renderCartesianPlotArea($type = 'textlin'): void
+    {
+        $this->graph = new Graph(self::$width, self::$height);
+        $this->graph->SetScale($type);
+
+        $this->renderTitle();
+
+        //    Rotate for bar rather than column chart
+        $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection();
+        $reverse = $rotation == 'bar';
+
+        $xAxisLabel = $this->chart->getXAxisLabel();
+        if ($xAxisLabel !== null) {
+            $title = $this->getCaption($xAxisLabel);
+            if ($title !== null) {
+                $this->graph->xaxis->SetTitle($title, 'center');
+                $this->graph->xaxis->title->SetMargin(35);
+                if ($reverse) {
+                    $this->graph->xaxis->title->SetAngle(90);
+                    $this->graph->xaxis->title->SetMargin(90);
+                }
+            }
+        }
+
+        $yAxisLabel = $this->chart->getYAxisLabel();
+        if ($yAxisLabel !== null) {
+            $title = $this->getCaption($yAxisLabel);
+            if ($title !== null) {
+                $this->graph->yaxis->SetTitle($title, 'center');
+                if ($reverse) {
+                    $this->graph->yaxis->title->SetAngle(0);
+                    $this->graph->yaxis->title->SetMargin(-55);
+                }
+            }
+        }
+    }
+
+    private function renderPiePlotArea(): void
+    {
+        $this->graph = new PieGraph(self::$width, self::$height);
+
+        $this->renderTitle();
+    }
+
+    private function renderRadarPlotArea(): void
+    {
+        $this->graph = new RadarGraph(self::$width, self::$height);
+        $this->graph->SetScale('lin');
+
+        $this->renderTitle();
+    }
+
+    private function renderPlotLine($groupID, $filled = false, $combination = false): void
+    {
+        $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
+
+        $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0];
+        $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount();
+        if ($labelCount > 0) {
+            $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
+            $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels);
+            $this->graph->xaxis->SetTickLabels($datasetLabels);
+        }
+
+        $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+        $seriesPlots = [];
+        if ($grouping == 'percentStacked') {
+            $sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
+        } else {
+            $sumValues = [];
+        }
+
+        //    Loop through each data series in turn
+        for ($i = 0; $i < $seriesCount; ++$i) {
+            $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$i];
+            $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues();
+            $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointMarker();
+
+            if ($grouping == 'percentStacked') {
+                $dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
+            }
+
+            //    Fill in any missing values in the $dataValues array
+            $testCurrentIndex = 0;
+            foreach ($dataValues as $k => $dataValue) {
+                while ($k != $testCurrentIndex) {
+                    $dataValues[$testCurrentIndex] = null;
+                    ++$testCurrentIndex;
+                }
+                ++$testCurrentIndex;
+            }
+
+            $seriesPlot = new LinePlot($dataValues);
+            if ($combination) {
+                $seriesPlot->SetBarCenter();
+            }
+
+            if ($filled) {
+                $seriesPlot->SetFilled(true);
+                $seriesPlot->SetColor('black');
+                $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
+            } else {
+                //    Set the appropriate plot marker
+                $this->formatPointMarker($seriesPlot, $marker);
+            }
+            $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($index)->getDataValue();
+            $seriesPlot->SetLegend($dataLabel);
+
+            $seriesPlots[] = $seriesPlot;
+        }
+
+        if ($grouping == 'standard') {
+            $groupPlot = $seriesPlots;
+        } else {
+            $groupPlot = new AccLinePlot($seriesPlots);
+        }
+        $this->graph->Add($groupPlot);
+    }
+
+    private function renderPlotBar($groupID, $dimensions = '2d'): void
+    {
+        $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection();
+        //    Rotate for bar rather than column chart
+        if (($groupID == 0) && ($rotation == 'bar')) {
+            $this->graph->Set90AndMargin();
+        }
+        $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
+
+        $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0];
+        $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount();
+        if ($labelCount > 0) {
+            $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
+            $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $rotation);
+            //    Rotate for bar rather than column chart
+            if ($rotation == 'bar') {
+                $datasetLabels = array_reverse($datasetLabels);
+                $this->graph->yaxis->SetPos('max');
+                $this->graph->yaxis->SetLabelAlign('center', 'top');
+                $this->graph->yaxis->SetLabelSide(SIDE_RIGHT);
+            }
+            $this->graph->xaxis->SetTickLabels($datasetLabels);
+        }
+
+        $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+        $seriesPlots = [];
+        if ($grouping == 'percentStacked') {
+            $sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
+        } else {
+            $sumValues = [];
+        }
+
+        //    Loop through each data series in turn
+        for ($j = 0; $j < $seriesCount; ++$j) {
+            $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$j];
+            $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues();
+            if ($grouping == 'percentStacked') {
+                $dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
+            }
+
+            //    Fill in any missing values in the $dataValues array
+            $testCurrentIndex = 0;
+            foreach ($dataValues as $k => $dataValue) {
+                while ($k != $testCurrentIndex) {
+                    $dataValues[$testCurrentIndex] = null;
+                    ++$testCurrentIndex;
+                }
+                ++$testCurrentIndex;
+            }
+
+            //    Reverse the $dataValues order for bar rather than column chart
+            if ($rotation == 'bar') {
+                $dataValues = array_reverse($dataValues);
+            }
+            $seriesPlot = new BarPlot($dataValues);
+            $seriesPlot->SetColor('black');
+            $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
+            if ($dimensions == '3d') {
+                $seriesPlot->SetShadow();
+            }
+            if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) {
+                $dataLabel = '';
+            } else {
+                $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue();
+            }
+            $seriesPlot->SetLegend($dataLabel);
+
+            $seriesPlots[] = $seriesPlot;
+        }
+        //    Reverse the plot order for bar rather than column chart
+        if (($rotation == 'bar') && ($grouping != 'percentStacked')) {
+            $seriesPlots = array_reverse($seriesPlots);
+        }
+
+        if ($grouping == 'clustered') {
+            $groupPlot = new GroupBarPlot($seriesPlots);
+        } elseif ($grouping == 'standard') {
+            $groupPlot = new GroupBarPlot($seriesPlots);
+        } else {
+            $groupPlot = new AccBarPlot($seriesPlots);
+            if ($dimensions == '3d') {
+                $groupPlot->SetShadow();
+            }
+        }
+
+        $this->graph->Add($groupPlot);
+    }
+
+    private function renderPlotScatter($groupID, $bubble): void
+    {
+        $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
+
+        $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+
+        //    Loop through each data series in turn
+        for ($i = 0; $i < $seriesCount; ++$i) {
+            $plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i);
+            if ($plotCategoryByIndex === false) {
+                $plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0);
+            }
+            $dataValuesY = $plotCategoryByIndex->getDataValues();
+            $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+
+            $redoDataValuesY = true;
+            if ($bubble) {
+                if (!$bubbleSize) {
+                    $bubbleSize = '10';
+                }
+                $redoDataValuesY = false;
+                foreach ($dataValuesY as $dataValueY) {
+                    if (!is_int($dataValueY) && !is_float($dataValueY)) {
+                        $redoDataValuesY = true;
+
+                        break;
+                    }
+                }
+            }
+            if ($redoDataValuesY) {
+                foreach ($dataValuesY as $k => $dataValueY) {
+                    $dataValuesY[$k] = $k;
+                }
+            }
+            //var_dump($dataValuesY, $dataValuesX, $bubbleSize);
+
+            $seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY);
+            if ($scatterStyle == 'lineMarker') {
+                $seriesPlot->SetLinkPoints();
+                $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]);
+            } elseif ($scatterStyle == 'smoothMarker') {
+                $spline = new Spline($dataValuesY, $dataValuesX);
+                [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20);
+                $lplot = new LinePlot($splineDataX, $splineDataY);
+                $lplot->SetColor(self::$colourSet[self::$plotColour]);
+
+                $this->graph->Add($lplot);
+            }
+
+            if ($bubble) {
+                $this->formatPointMarker($seriesPlot, 'dot');
+                $seriesPlot->mark->SetColor('black');
+                $seriesPlot->mark->SetSize($bubbleSize);
+            } else {
+                $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
+                $this->formatPointMarker($seriesPlot, $marker);
+            }
+            $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
+            $seriesPlot->SetLegend($dataLabel);
+
+            $this->graph->Add($seriesPlot);
+        }
+    }
+
+    private function renderPlotRadar($groupID): void
+    {
+        $radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
+
+        $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+
+        //    Loop through each data series in turn
+        for ($i = 0; $i < $seriesCount; ++$i) {
+            $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
+            $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+            $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
+
+            $dataValues = [];
+            foreach ($dataValuesY as $k => $dataValueY) {
+                $dataValues[$k] = is_array($dataValueY) ? implode(' ', array_reverse($dataValueY)) : $dataValueY;
+            }
+            $tmp = array_shift($dataValues);
+            $dataValues[] = $tmp;
+            $tmp = array_shift($dataValuesX);
+            $dataValuesX[] = $tmp;
+
+            $this->graph->SetTitles(array_reverse($dataValues));
+
+            $seriesPlot = new RadarPlot(array_reverse($dataValuesX));
+
+            $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
+            $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
+            if ($radarStyle == 'filled') {
+                $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]);
+            }
+            $this->formatPointMarker($seriesPlot, $marker);
+            $seriesPlot->SetLegend($dataLabel);
+
+            $this->graph->Add($seriesPlot);
+        }
+    }
+
+    private function renderPlotContour($groupID): void
+    {
+        $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+
+        $dataValues = [];
+        //    Loop through each data series in turn
+        for ($i = 0; $i < $seriesCount; ++$i) {
+            $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+
+            $dataValues[$i] = $dataValuesX;
+        }
+        $seriesPlot = new ContourPlot($dataValues);
+
+        $this->graph->Add($seriesPlot);
+    }
+
+    private function renderPlotStock($groupID): void
+    {
+        $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+        $plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder();
+
+        $dataValues = [];
+        //    Loop through each data series in turn and build the plot arrays
+        foreach ($plotOrder as $i => $v) {
+            $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v);
+            if ($dataValuesX === false) {
+                continue;
+            }
+            $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues();
+            foreach ($dataValuesX as $j => $dataValueX) {
+                $dataValues[$plotOrder[$i]][$j] = $dataValueX;
+            }
+        }
+        if (empty($dataValues)) {
+            return;
+        }
+
+        $dataValuesPlot = [];
+        // Flatten the plot arrays to a single dimensional array to work with jpgraph
+        $jMax = count($dataValues[0]);
+        for ($j = 0; $j < $jMax; ++$j) {
+            for ($i = 0; $i < $seriesCount; ++$i) {
+                $dataValuesPlot[] = $dataValues[$i][$j] ?? null;
+            }
+        }
+
+        // Set the x-axis labels
+        $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
+        if ($labelCount > 0) {
+            $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
+            $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels);
+            $this->graph->xaxis->SetTickLabels($datasetLabels);
+        }
+
+        $seriesPlot = new StockPlot($dataValuesPlot);
+        $seriesPlot->SetWidth(20);
+
+        $this->graph->Add($seriesPlot);
+    }
+
+    private function renderAreaChart($groupCount): void
+    {
+        $this->renderCartesianPlotArea();
+
+        for ($i = 0; $i < $groupCount; ++$i) {
+            $this->renderPlotLine($i, true, false);
+        }
+    }
+
+    private function renderLineChart($groupCount): void
+    {
+        $this->renderCartesianPlotArea();
+
+        for ($i = 0; $i < $groupCount; ++$i) {
+            $this->renderPlotLine($i, false, false);
+        }
+    }
+
+    private function renderBarChart($groupCount, $dimensions = '2d'): void
+    {
+        $this->renderCartesianPlotArea();
+
+        for ($i = 0; $i < $groupCount; ++$i) {
+            $this->renderPlotBar($i, $dimensions);
+        }
+    }
+
+    private function renderScatterChart($groupCount): void
+    {
+        $this->renderCartesianPlotArea('linlin');
+
+        for ($i = 0; $i < $groupCount; ++$i) {
+            $this->renderPlotScatter($i, false);
+        }
+    }
+
+    private function renderBubbleChart($groupCount): void
+    {
+        $this->renderCartesianPlotArea('linlin');
+
+        for ($i = 0; $i < $groupCount; ++$i) {
+            $this->renderPlotScatter($i, true);
+        }
+    }
+
+    private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false): void
+    {
+        $this->renderPiePlotArea();
+
+        $iLimit = ($multiplePlots) ? $groupCount : 1;
+        for ($groupID = 0; $groupID < $iLimit; ++$groupID) {
+            $exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
+            $datasetLabels = [];
+            if ($groupID == 0) {
+                $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
+                if ($labelCount > 0) {
+                    $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
+                    $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels);
+                }
+            }
+
+            $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+            //    For pie charts, we only display the first series: doughnut charts generally display all series
+            $jLimit = ($multiplePlots) ? $seriesCount : 1;
+            //    Loop through each data series in turn
+            for ($j = 0; $j < $jLimit; ++$j) {
+                $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues();
+
+                //    Fill in any missing values in the $dataValues array
+                $testCurrentIndex = 0;
+                foreach ($dataValues as $k => $dataValue) {
+                    while ($k != $testCurrentIndex) {
+                        $dataValues[$testCurrentIndex] = null;
+                        ++$testCurrentIndex;
+                    }
+                    ++$testCurrentIndex;
+                }
+
+                if ($dimensions == '3d') {
+                    $seriesPlot = new PiePlot3D($dataValues);
+                } else {
+                    if ($doughnut) {
+                        $seriesPlot = new PiePlotC($dataValues);
+                    } else {
+                        $seriesPlot = new PiePlot($dataValues);
+                    }
+                }
+
+                if ($multiplePlots) {
+                    $seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4));
+                }
+
+                if ($doughnut && method_exists($seriesPlot, 'SetMidColor')) {
+                    $seriesPlot->SetMidColor('white');
+                }
+
+                $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
+                if (count($datasetLabels) > 0) {
+                    $seriesPlot->SetLabels(array_fill(0, count($datasetLabels), ''));
+                }
+                if ($dimensions != '3d') {
+                    $seriesPlot->SetGuideLines(false);
+                }
+                if ($j == 0) {
+                    if ($exploded) {
+                        $seriesPlot->ExplodeAll();
+                    }
+                    $seriesPlot->SetLegends($datasetLabels);
+                }
+
+                $this->graph->Add($seriesPlot);
+            }
+        }
+    }
+
+    private function renderRadarChart($groupCount): void
+    {
+        $this->renderRadarPlotArea();
+
+        for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
+            $this->renderPlotRadar($groupID);
+        }
+    }
+
+    private function renderStockChart($groupCount): void
+    {
+        $this->renderCartesianPlotArea('intint');
+
+        for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
+            $this->renderPlotStock($groupID);
+        }
+    }
+
+    private function renderContourChart($groupCount): void
+    {
+        $this->renderCartesianPlotArea('intint');
+
+        for ($i = 0; $i < $groupCount; ++$i) {
+            $this->renderPlotContour($i);
+        }
+    }
+
+    private function renderCombinationChart($groupCount, $outputDestination)
+    {
+        $this->renderCartesianPlotArea();
+
+        for ($i = 0; $i < $groupCount; ++$i) {
+            $dimensions = null;
+            $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
+            switch ($chartType) {
+                case 'area3DChart':
+                case 'areaChart':
+                    $this->renderPlotLine($i, true, true);
+
+                    break;
+                case 'bar3DChart':
+                    $dimensions = '3d';
+                    // no break
+                case 'barChart':
+                    $this->renderPlotBar($i, $dimensions);
+
+                    break;
+                case 'line3DChart':
+                case 'lineChart':
+                    $this->renderPlotLine($i, false, true);
+
+                    break;
+                case 'scatterChart':
+                    $this->renderPlotScatter($i, false);
+
+                    break;
+                case 'bubbleChart':
+                    $this->renderPlotScatter($i, true);
+
+                    break;
+                default:
+                    $this->graph = null;
+
+                    return false;
+            }
+        }
+
+        $this->renderLegend();
+
+        $this->graph->Stroke($outputDestination);
+
+        return true;
+    }
+
+    public function render($outputDestination)
+    {
+        self::$plotColour = 0;
+
+        $groupCount = $this->chart->getPlotArea()->getPlotGroupCount();
+
+        $dimensions = null;
+        if ($groupCount == 1) {
+            $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType();
+        } else {
+            $chartTypes = [];
+            for ($i = 0; $i < $groupCount; ++$i) {
+                $chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
+            }
+            $chartTypes = array_unique($chartTypes);
+            if (count($chartTypes) == 1) {
+                $chartType = array_pop($chartTypes);
+            } elseif (count($chartTypes) == 0) {
+                echo 'Chart is not yet implemented<br />';
+
+                return false;
+            } else {
+                return $this->renderCombinationChart($groupCount, $outputDestination);
+            }
+        }
+
+        switch ($chartType) {
+            case 'area3DChart':
+                $dimensions = '3d';
+                // no break
+            case 'areaChart':
+                $this->renderAreaChart($groupCount);
+
+                break;
+            case 'bar3DChart':
+                $dimensions = '3d';
+                // no break
+            case 'barChart':
+                $this->renderBarChart($groupCount, $dimensions);
+
+                break;
+            case 'line3DChart':
+                $dimensions = '3d';
+                // no break
+            case 'lineChart':
+                $this->renderLineChart($groupCount);
+
+                break;
+            case 'pie3DChart':
+                $dimensions = '3d';
+                // no break
+            case 'pieChart':
+                $this->renderPieChart($groupCount, $dimensions, false, false);
+
+                break;
+            case 'doughnut3DChart':
+                $dimensions = '3d';
+                // no break
+            case 'doughnutChart':
+                $this->renderPieChart($groupCount, $dimensions, true, true);
+
+                break;
+            case 'scatterChart':
+                $this->renderScatterChart($groupCount);
+
+                break;
+            case 'bubbleChart':
+                $this->renderBubbleChart($groupCount);
+
+                break;
+            case 'radarChart':
+                $this->renderRadarChart($groupCount);
+
+                break;
+            case 'surface3DChart':
+            case 'surfaceChart':
+                $this->renderContourChart($groupCount);
+
+                break;
+            case 'stockChart':
+                $this->renderStockChart($groupCount);
+
+                break;
+            default:
+                echo $chartType . ' is not yet implemented<br />';
+
+                return false;
+        }
+        $this->renderLegend();
+
+        $this->graph->Stroke($outputDestination);
+
+        return true;
+    }
+}

+ 36 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
+
+/**
+ * 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 June 2023 and has PHP 8.2 support.
+ */
+class MtJpGraphRenderer extends JpGraphRendererBase
+{
+    protected static function init(): void
+    {
+        static $loaded = false;
+        if ($loaded) {
+            return;
+        }
+
+        \mitoteam\jpgraph\MtJpGraph::load([
+            'bar',
+            'contour',
+            'line',
+            'pie',
+            'pie3d',
+            'radar',
+            'regstat',
+            'scatter',
+            'stock',
+        ], true); // enable Extended mode
+
+        $loaded = true;
+    }
+}

+ 226 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php

@@ -0,0 +1,226 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+class TrendLine extends Properties
+{
+    const TRENDLINE_EXPONENTIAL = 'exp';
+    const TRENDLINE_LINEAR = 'linear';
+    const TRENDLINE_LOGARITHMIC = 'log';
+    const TRENDLINE_POLYNOMIAL = 'poly'; // + 'order'
+    const TRENDLINE_POWER = 'power';
+    const TRENDLINE_MOVING_AVG = 'movingAvg'; // + 'period'
+    const TRENDLINE_TYPES = [
+        self::TRENDLINE_EXPONENTIAL,
+        self::TRENDLINE_LINEAR,
+        self::TRENDLINE_LOGARITHMIC,
+        self::TRENDLINE_POLYNOMIAL,
+        self::TRENDLINE_POWER,
+        self::TRENDLINE_MOVING_AVG,
+    ];
+
+    /** @var string */
+    private $trendLineType = 'linear'; // TRENDLINE_LINEAR
+
+    /** @var int */
+    private $order = 2;
+
+    /** @var int */
+    private $period = 3;
+
+    /** @var bool */
+    private $dispRSqr = false;
+
+    /** @var bool */
+    private $dispEq = false;
+
+    /** @var string */
+    private $name = '';
+
+    /** @var float */
+    private $backward = 0.0;
+
+    /** @var float */
+    private $forward = 0.0;
+
+    /** @var float */
+    private $intercept = 0.0;
+
+    /**
+     * Create a new TrendLine object.
+     */
+    public function __construct(
+        string $trendLineType = '',
+        ?int $order = null,
+        ?int $period = null,
+        bool $dispRSqr = false,
+        bool $dispEq = false,
+        ?float $backward = null,
+        ?float $forward = null,
+        ?float $intercept = null,
+        ?string $name = null
+    ) {
+        parent::__construct();
+        $this->setTrendLineProperties(
+            $trendLineType,
+            $order,
+            $period,
+            $dispRSqr,
+            $dispEq,
+            $backward,
+            $forward,
+            $intercept,
+            $name
+        );
+    }
+
+    public function getTrendLineType(): string
+    {
+        return $this->trendLineType;
+    }
+
+    public function setTrendLineType(string $trendLineType): self
+    {
+        $this->trendLineType = $trendLineType;
+
+        return $this;
+    }
+
+    public function getOrder(): int
+    {
+        return $this->order;
+    }
+
+    public function setOrder(int $order): self
+    {
+        $this->order = $order;
+
+        return $this;
+    }
+
+    public function getPeriod(): int
+    {
+        return $this->period;
+    }
+
+    public function setPeriod(int $period): self
+    {
+        $this->period = $period;
+
+        return $this;
+    }
+
+    public function getDispRSqr(): bool
+    {
+        return $this->dispRSqr;
+    }
+
+    public function setDispRSqr(bool $dispRSqr): self
+    {
+        $this->dispRSqr = $dispRSqr;
+
+        return $this;
+    }
+
+    public function getDispEq(): bool
+    {
+        return $this->dispEq;
+    }
+
+    public function setDispEq(bool $dispEq): self
+    {
+        $this->dispEq = $dispEq;
+
+        return $this;
+    }
+
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    public function setName(string $name): self
+    {
+        $this->name = $name;
+
+        return $this;
+    }
+
+    public function getBackward(): float
+    {
+        return $this->backward;
+    }
+
+    public function setBackward(float $backward): self
+    {
+        $this->backward = $backward;
+
+        return $this;
+    }
+
+    public function getForward(): float
+    {
+        return $this->forward;
+    }
+
+    public function setForward(float $forward): self
+    {
+        $this->forward = $forward;
+
+        return $this;
+    }
+
+    public function getIntercept(): float
+    {
+        return $this->intercept;
+    }
+
+    public function setIntercept(float $intercept): self
+    {
+        $this->intercept = $intercept;
+
+        return $this;
+    }
+
+    public function setTrendLineProperties(
+        ?string $trendLineType = null,
+        ?int $order = 0,
+        ?int $period = 0,
+        ?bool $dispRSqr = false,
+        ?bool $dispEq = false,
+        ?float $backward = null,
+        ?float $forward = null,
+        ?float $intercept = null,
+        ?string $name = null
+    ): self {
+        if (!empty($trendLineType)) {
+            $this->setTrendLineType($trendLineType);
+        }
+        if ($order !== null) {
+            $this->setOrder($order);
+        }
+        if ($period !== null) {
+            $this->setPeriod($period);
+        }
+        if ($dispRSqr !== null) {
+            $this->setDispRSqr($dispRSqr);
+        }
+        if ($dispEq !== null) {
+            $this->setDispEq($dispEq);
+        }
+        if ($backward !== null) {
+            $this->setBackward($backward);
+        }
+        if ($forward !== null) {
+            $this->setForward($forward);
+        }
+        if ($intercept !== null) {
+            $this->setIntercept($intercept);
+        }
+        if ($name !== null) {
+            $this->setName($name);
+        }
+
+        return $this;
+    }
+}

+ 126 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php

@@ -0,0 +1,126 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Collection\Memory;
+
+use DateInterval;
+use Psr\SimpleCache\CacheInterface;
+
+/**
+ * This is the default implementation for in-memory cell collection.
+ *
+ * Alternatives implementation should leverage off-memory, non-volatile storage
+ * to reduce overall memory usage.
+ */
+class SimpleCache1 implements CacheInterface
+{
+    /**
+     * @var array Cell Cache
+     */
+    private $cache = [];
+
+    /**
+     * @return bool
+     */
+    public function clear()
+    {
+        $this->cache = [];
+
+        return true;
+    }
+
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function delete($key)
+    {
+        unset($this->cache[$key]);
+
+        return true;
+    }
+
+    /**
+     * @param iterable $keys
+     *
+     * @return bool
+     */
+    public function deleteMultiple($keys)
+    {
+        foreach ($keys as $key) {
+            $this->delete($key);
+        }
+
+        return true;
+    }
+
+    /**
+     * @param string $key
+     * @param mixed  $default
+     *
+     * @return mixed
+     */
+    public function get($key, $default = null)
+    {
+        if ($this->has($key)) {
+            return $this->cache[$key];
+        }
+
+        return $default;
+    }
+
+    /**
+     * @param iterable $keys
+     * @param mixed    $default
+     *
+     * @return iterable
+     */
+    public function getMultiple($keys, $default = null)
+    {
+        $results = [];
+        foreach ($keys as $key) {
+            $results[$key] = $this->get($key, $default);
+        }
+
+        return $results;
+    }
+
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function has($key)
+    {
+        return array_key_exists($key, $this->cache);
+    }
+
+    /**
+     * @param string                 $key
+     * @param mixed                  $value
+     * @param null|DateInterval|int $ttl
+     *
+     * @return bool
+     */
+    public function set($key, $value, $ttl = null)
+    {
+        $this->cache[$key] = $value;
+
+        return true;
+    }
+
+    /**
+     * @param iterable               $values
+     * @param null|DateInterval|int $ttl
+     *
+     * @return bool
+     */
+    public function setMultiple($values, $ttl = null)
+    {
+        foreach ($values as $key => $value) {
+            $this->set($key, $value);
+        }
+
+        return true;
+    }
+}

+ 109 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php

@@ -0,0 +1,109 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Collection\Memory;
+
+use DateInterval;
+use Psr\SimpleCache\CacheInterface;
+
+/**
+ * This is the default implementation for in-memory cell collection.
+ *
+ * Alternatives implementation should leverage off-memory, non-volatile storage
+ * to reduce overall memory usage.
+ */
+class SimpleCache3 implements CacheInterface
+{
+    /**
+     * @var array Cell Cache
+     */
+    private $cache = [];
+
+    public function clear(): bool
+    {
+        $this->cache = [];
+
+        return true;
+    }
+
+    /**
+     * @param string $key
+     */
+    public function delete($key): bool
+    {
+        unset($this->cache[$key]);
+
+        return true;
+    }
+
+    /**
+     * @param iterable $keys
+     */
+    public function deleteMultiple($keys): bool
+    {
+        foreach ($keys as $key) {
+            $this->delete($key);
+        }
+
+        return true;
+    }
+
+    /**
+     * @param string $key
+     * @param mixed  $default
+     */
+    public function get($key, $default = null): mixed
+    {
+        if ($this->has($key)) {
+            return $this->cache[$key];
+        }
+
+        return $default;
+    }
+
+    /**
+     * @param iterable $keys
+     * @param mixed    $default
+     */
+    public function getMultiple($keys, $default = null): iterable
+    {
+        $results = [];
+        foreach ($keys as $key) {
+            $results[$key] = $this->get($key, $default);
+        }
+
+        return $results;
+    }
+
+    /**
+     * @param string $key
+     */
+    public function has($key): bool
+    {
+        return array_key_exists($key, $this->cache);
+    }
+
+    /**
+     * @param string                 $key
+     * @param mixed                  $value
+     * @param null|DateInterval|int $ttl
+     */
+    public function set($key, $value, $ttl = null): bool
+    {
+        $this->cache[$key] = $value;
+
+        return true;
+    }
+
+    /**
+     * @param iterable               $values
+     * @param null|DateInterval|int $ttl
+     */
+    public function setMultiple($values, $ttl = null): bool
+    {
+        foreach ($values as $key => $value) {
+            $this->set($key, $value);
+        }
+
+        return true;
+    }
+}

+ 89 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Helper;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+
+class Downloader
+{
+    protected string $filepath;
+
+    protected string $filename;
+
+    protected string $filetype;
+
+    protected const CONTENT_TYPES = [
+        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+        'xls' => 'application/vnd.ms-excel',
+        'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+        'csv' => 'text/csv',
+        'html' => 'text/html',
+        'pdf' => 'application/pdf',
+    ];
+
+    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");
+        }
+
+        $filetype ??= pathinfo($filename, PATHINFO_EXTENSION);
+        if (array_key_exists(strtolower($filetype), self::CONTENT_TYPES) === false) {
+            throw new Exception("Invalid filetype: {$filetype} cannot be downloaded");
+        }
+        $this->filetype = strtolower($filetype);
+    }
+
+    public function download(): void
+    {
+        $this->headers();
+
+        readfile($this->filepath);
+    }
+
+    public function headers(): void
+    {
+        ob_clean();
+
+        $this->contentType();
+        $this->contentDisposition();
+        $this->cacheHeaders();
+        $this->fileSize();
+
+        flush();
+    }
+
+    protected function contentType(): void
+    {
+        header('Content-Type: ' . self::CONTENT_TYPES[$this->filetype]);
+    }
+
+    protected function contentDisposition(): void
+    {
+        header('Content-Disposition: attachment;filename="' . $this->filename . '"');
+    }
+
+    protected function cacheHeaders(): void
+    {
+        header('Cache-Control: max-age=0');
+        // If you're serving to IE 9, then the following may be needed
+        header('Cache-Control: max-age=1');
+
+        // If you're serving to IE over SSL, then the following may be needed
+        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
+        header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified
+        header('Cache-Control: cache, must-revalidate'); // HTTP/1.1
+        header('Pragma: public'); // HTTP/1.0
+    }
+
+    protected function fileSize(): void
+    {
+        header('Content-Length: ' . filesize($this->filepath));
+    }
+}

+ 46 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Helper;
+
+class Handler
+{
+    /** @var string */
+    private static $invalidHex = 'Y';
+
+    // A bunch of methods to show that we continue
+    // to capture messages even using PhpUnit 10.
+    public static function suppressed(): bool
+    {
+        return @trigger_error('hello');
+    }
+
+    public static function deprecated(): string
+    {
+        return (string) hexdec(self::$invalidHex);
+    }
+
+    public static function notice(string $value): void
+    {
+        date_default_timezone_set($value);
+    }
+
+    public static function warning(): bool
+    {
+        return file_get_contents(__FILE__ . 'noexist') !== false;
+    }
+
+    public static function userDeprecated(): bool
+    {
+        return trigger_error('hello', E_USER_DEPRECATED);
+    }
+
+    public static function userNotice(): bool
+    {
+        return trigger_error('userNotice', E_USER_NOTICE);
+    }
+
+    public static function userWarning(): bool
+    {
+        return trigger_error('userWarning', E_USER_WARNING);
+    }
+}

+ 139 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php

@@ -0,0 +1,139 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Helper;
+
+class TextGrid
+{
+    /**
+     * @var bool
+     */
+    private $isCli = true;
+
+    /**
+     * @var array
+     */
+    protected $matrix;
+
+    /**
+     * @var array
+     */
+    protected $rows;
+
+    /**
+     * @var array
+     */
+    protected $columns;
+
+    /**
+     * @var string
+     */
+    private $gridDisplay;
+
+    public function __construct(array $matrix, bool $isCli = true)
+    {
+        $this->rows = array_keys($matrix);
+        $this->columns = array_keys($matrix[$this->rows[0]]);
+
+        $matrix = array_values($matrix);
+        array_walk(
+            $matrix,
+            function (&$row): void {
+                $row = array_values($row);
+            }
+        );
+
+        $this->matrix = $matrix;
+        $this->isCli = $isCli;
+    }
+
+    public function render(): string
+    {
+        $this->gridDisplay = $this->isCli ? '' : '<pre>';
+
+        $maxRow = max($this->rows);
+        $maxRowLength = mb_strlen((string) $maxRow) + 1;
+        $columnWidths = $this->getColumnWidths();
+
+        $this->renderColumnHeader($maxRowLength, $columnWidths);
+        $this->renderRows($maxRowLength, $columnWidths);
+        $this->renderFooter($maxRowLength, $columnWidths);
+
+        $this->gridDisplay .= $this->isCli ? '' : '</pre>';
+
+        return $this->gridDisplay;
+    }
+
+    private function renderRows(int $maxRowLength, array $columnWidths): void
+    {
+        foreach ($this->matrix as $row => $rowData) {
+            $this->gridDisplay .= '|' . str_pad((string) $this->rows[$row], $maxRowLength, ' ', STR_PAD_LEFT) . ' ';
+            $this->renderCells($rowData, $columnWidths);
+            $this->gridDisplay .= '|' . PHP_EOL;
+        }
+    }
+
+    private function renderCells(array $rowData, array $columnWidths): void
+    {
+        foreach ($rowData as $column => $cell) {
+            $displayCell = ($this->isCli) ? (string) $cell : htmlentities((string) $cell);
+            $this->gridDisplay .= '| ';
+            $this->gridDisplay .= $displayCell . str_repeat(' ', $columnWidths[$column] - mb_strlen($cell ?? '') + 1);
+        }
+    }
+
+    private function renderColumnHeader(int $maxRowLength, array $columnWidths): void
+    {
+        $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2);
+        foreach ($this->columns as $column => $reference) {
+            $this->gridDisplay .= '+-' . str_repeat('-', $columnWidths[$column] + 1);
+        }
+        $this->gridDisplay .= '+' . PHP_EOL;
+
+        $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2);
+        foreach ($this->columns as $column => $reference) {
+            $this->gridDisplay .= '| ' . str_pad((string) $reference, $columnWidths[$column] + 1, ' ');
+        }
+        $this->gridDisplay .= '|' . PHP_EOL;
+
+        $this->renderFooter($maxRowLength, $columnWidths);
+    }
+
+    private function renderFooter(int $maxRowLength, array $columnWidths): void
+    {
+        $this->gridDisplay .= '+' . str_repeat('-', $maxRowLength + 1);
+        foreach ($this->columns as $column => $reference) {
+            $this->gridDisplay .= '+-';
+            $this->gridDisplay .= str_pad((string) '', $columnWidths[$column] + 1, '-');
+        }
+        $this->gridDisplay .= '+' . PHP_EOL;
+    }
+
+    private function getColumnWidths(): array
+    {
+        $columnCount = count($this->matrix, COUNT_RECURSIVE) / count($this->matrix);
+        $columnWidths = [];
+        for ($column = 0; $column < $columnCount; ++$column) {
+            $columnWidths[] = $this->getColumnWidth(array_column($this->matrix, $column));
+        }
+
+        return $columnWidths;
+    }
+
+    private function getColumnWidth(array $columnData): int
+    {
+        $columnWidth = 0;
+        $columnData = array_values($columnData);
+
+        foreach ($columnData as $columnValue) {
+            if (is_string($columnValue)) {
+                $columnWidth = max($columnWidth, mb_strlen($columnValue));
+            } elseif (is_bool($columnValue)) {
+                $columnWidth = max($columnWidth, mb_strlen($columnValue ? 'TRUE' : 'FALSE'));
+            }
+
+            $columnWidth = max($columnWidth, mb_strlen((string) $columnWidth));
+        }
+
+        return $columnWidth;
+    }
+}

+ 27 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseLoader.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
+
+use DOMElement;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+abstract class BaseLoader
+{
+    /**
+     * @var Spreadsheet
+     */
+    protected $spreadsheet;
+
+    /**
+     * @var string
+     */
+    protected $tableNs;
+
+    public function __construct(Spreadsheet $spreadsheet, string $tableNs)
+    {
+        $this->spreadsheet = $spreadsheet;
+        $this->tableNs = $tableNs;
+    }
+
+    abstract public function read(DOMElement $workbookData): void;
+}

+ 97 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+
+class FormulaTranslator
+{
+    public static function convertToExcelAddressValue(string $openOfficeAddress): string
+    {
+        $excelAddress = $openOfficeAddress;
+
+        // Cell range 3-d reference
+        // As we don't support 3-d ranges, we're just going to take a quick and dirty approach
+        //  and assume that the second worksheet reference is the same as the first
+        $excelAddress = (string) preg_replace(
+            [
+                '/\$?([^\.]+)\.([^\.]+):\$?([^\.]+)\.([^\.]+)/miu',
+                '/\$?([^\.]+)\.([^\.]+):\.([^\.]+)/miu', // Cell range reference in another sheet
+                '/\$?([^\.]+)\.([^\.]+)/miu', // Cell reference in another sheet
+                '/\.([^\.]+):\.([^\.]+)/miu', // Cell range reference
+                '/\.([^\.]+)/miu', // Simple cell reference
+            ],
+            [
+                '$1!$2:$4',
+                '$1!$2:$3',
+                '$1!$2',
+                '$1:$2',
+                '$1',
+            ],
+            $excelAddress
+        );
+
+        return $excelAddress;
+    }
+
+    public static function convertToExcelFormulaValue(string $openOfficeFormula): string
+    {
+        $temp = explode(Calculation::FORMULA_STRING_QUOTE, $openOfficeFormula);
+        $tKey = false;
+        $inMatrixBracesLevel = 0;
+        $inFunctionBracesLevel = 0;
+        foreach ($temp as &$value) {
+            // @var string $value
+            // Only replace in alternate array entries (i.e. non-quoted blocks)
+            //      so that conversion isn't done in string values
+            $tKey = $tKey === false;
+            if ($tKey) {
+                $value = (string) preg_replace(
+                    [
+                        '/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', // Cell range reference in another sheet
+                        '/\[\$?([^\.]+)\.([^\.]+)\]/miu', // Cell reference in another sheet
+                        '/\[\.([^\.]+):\.([^\.]+)\]/miu', // Cell range reference
+                        '/\[\.([^\.]+)\]/miu', // Simple cell reference
+                    ],
+                    [
+                        '$1!$2:$3',
+                        '$1!$2',
+                        '$1:$2',
+                        '$1',
+                    ],
+                    $value
+                );
+                // Convert references to defined names/formulae
+                $value = str_replace('$$', '', $value);
+
+                // Convert ODS function argument separators to Excel function argument separators
+                $value = Calculation::translateSeparator(';', ',', $value, $inFunctionBracesLevel);
+
+                // Convert ODS matrix separators to Excel matrix separators
+                $value = Calculation::translateSeparator(
+                    ';',
+                    ',',
+                    $value,
+                    $inMatrixBracesLevel,
+                    Calculation::FORMULA_OPEN_MATRIX_BRACE,
+                    Calculation::FORMULA_CLOSE_MATRIX_BRACE
+                );
+                $value = Calculation::translateSeparator(
+                    '|',
+                    ';',
+                    $value,
+                    $inMatrixBracesLevel,
+                    Calculation::FORMULA_OPEN_MATRIX_BRACE,
+                    Calculation::FORMULA_CLOSE_MATRIX_BRACE
+                );
+
+                $value = (string) preg_replace('/COM\.MICROSOFT\./ui', '', $value);
+            }
+        }
+
+        // Then rebuild the formula string
+        $excelFormula = implode('"', $temp);
+
+        return $excelFormula;
+    }
+}

+ 49 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
+
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+
+class ConditionalFormatting
+{
+    /**
+     * @var array<int, string>
+     */
+    private static $types = [
+        0x01 => Conditional::CONDITION_CELLIS,
+        0x02 => Conditional::CONDITION_EXPRESSION,
+    ];
+
+    /**
+     * @var array<int, string>
+     */
+    private static $operators = [
+        0x00 => Conditional::OPERATOR_NONE,
+        0x01 => Conditional::OPERATOR_BETWEEN,
+        0x02 => Conditional::OPERATOR_NOTBETWEEN,
+        0x03 => Conditional::OPERATOR_EQUAL,
+        0x04 => Conditional::OPERATOR_NOTEQUAL,
+        0x05 => Conditional::OPERATOR_GREATERTHAN,
+        0x06 => Conditional::OPERATOR_LESSTHAN,
+        0x07 => Conditional::OPERATOR_GREATERTHANOREQUAL,
+        0x08 => Conditional::OPERATOR_LESSTHANOREQUAL,
+    ];
+
+    public static function type(int $type): ?string
+    {
+        if (isset(self::$types[$type])) {
+            return self::$types[$type];
+        }
+
+        return null;
+    }
+
+    public static function operator(int $operator): ?string
+    {
+        if (isset(self::$operators[$operator])) {
+            return self::$operators[$operator];
+        }
+
+        return null;
+    }
+}

+ 72 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
+
+use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
+
+class DataValidationHelper
+{
+    /**
+     * @var array<int, string>
+     */
+    private static $types = [
+        0x00 => DataValidation::TYPE_NONE,
+        0x01 => DataValidation::TYPE_WHOLE,
+        0x02 => DataValidation::TYPE_DECIMAL,
+        0x03 => DataValidation::TYPE_LIST,
+        0x04 => DataValidation::TYPE_DATE,
+        0x05 => DataValidation::TYPE_TIME,
+        0x06 => DataValidation::TYPE_TEXTLENGTH,
+        0x07 => DataValidation::TYPE_CUSTOM,
+    ];
+
+    /**
+     * @var array<int, string>
+     */
+    private static $errorStyles = [
+        0x00 => DataValidation::STYLE_STOP,
+        0x01 => DataValidation::STYLE_WARNING,
+        0x02 => DataValidation::STYLE_INFORMATION,
+    ];
+
+    /**
+     * @var array<int, string>
+     */
+    private static $operators = [
+        0x00 => DataValidation::OPERATOR_BETWEEN,
+        0x01 => DataValidation::OPERATOR_NOTBETWEEN,
+        0x02 => DataValidation::OPERATOR_EQUAL,
+        0x03 => DataValidation::OPERATOR_NOTEQUAL,
+        0x04 => DataValidation::OPERATOR_GREATERTHAN,
+        0x05 => DataValidation::OPERATOR_LESSTHAN,
+        0x06 => DataValidation::OPERATOR_GREATERTHANOREQUAL,
+        0x07 => DataValidation::OPERATOR_LESSTHANOREQUAL,
+    ];
+
+    public static function type(int $type): ?string
+    {
+        if (isset(self::$types[$type])) {
+            return self::$types[$type];
+        }
+
+        return null;
+    }
+
+    public static function errorStyle(int $errorStyle): ?string
+    {
+        if (isset(self::$errorStyles[$errorStyle])) {
+            return self::$errorStyles[$errorStyle];
+        }
+
+        return null;
+    }
+
+    public static function operator(int $operator): ?string
+    {
+        if (isset(self::$operators[$operator])) {
+            return self::$operators[$operator];
+        }
+
+        return null;
+    }
+}

+ 26 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+class SharedFormula
+{
+    private string $master;
+
+    private string $formula;
+
+    public function __construct(string $master, string $formula)
+    {
+        $this->master = $master;
+        $this->formula = $formula;
+    }
+
+    public function master(): string
+    {
+        return $this->master;
+    }
+
+    public function formula(): string
+    {
+        return $this->formula;
+    }
+}

+ 113 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php

@@ -0,0 +1,113 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\Table;
+use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use SimpleXMLElement;
+
+class TableReader
+{
+    /**
+     * @var Worksheet
+     */
+    private $worksheet;
+
+    /**
+     * @var SimpleXMLElement
+     */
+    private $tableXml;
+
+    public function __construct(Worksheet $workSheet, SimpleXMLElement $tableXml)
+    {
+        $this->worksheet = $workSheet;
+        $this->tableXml = $tableXml;
+    }
+
+    /**
+     * Loads Table into the Worksheet.
+     */
+    public function load(): void
+    {
+        // Remove all "$" in the table range
+        $tableRange = (string) preg_replace('/\$/', '', $this->tableXml['ref'] ?? '');
+        if (strpos($tableRange, ':') !== false) {
+            $this->readTable($tableRange, $this->tableXml);
+        }
+    }
+
+    /**
+     * Read Table from xml.
+     */
+    private function readTable(string $tableRange, SimpleXMLElement $tableXml): void
+    {
+        $table = new Table($tableRange);
+        $table->setName((string) $tableXml['displayName']);
+        $table->setShowHeaderRow((string) $tableXml['headerRowCount'] !== '0');
+        $table->setShowTotalsRow((string) $tableXml['totalsRowCount'] === '1');
+
+        $this->readTableAutoFilter($table, $tableXml->autoFilter);
+        $this->readTableColumns($table, $tableXml->tableColumns);
+        $this->readTableStyle($table, $tableXml->tableStyleInfo);
+
+        (new AutoFilter($table, $tableXml))->load();
+        $this->worksheet->addTable($table);
+    }
+
+    /**
+     * Reads TableAutoFilter from xml.
+     */
+    private function readTableAutoFilter(Table $table, SimpleXMLElement $autoFilterXml): void
+    {
+        if ($autoFilterXml->filterColumn === null) {
+            $table->setAllowFilter(false);
+
+            return;
+        }
+
+        foreach ($autoFilterXml->filterColumn as $filterColumn) {
+            $column = $table->getColumnByOffset((int) $filterColumn['colId']);
+            $column->setShowFilterButton((string) $filterColumn['hiddenButton'] !== '1');
+        }
+    }
+
+    /**
+     * Reads TableColumns from xml.
+     */
+    private function readTableColumns(Table $table, SimpleXMLElement $tableColumnsXml): void
+    {
+        $offset = 0;
+        foreach ($tableColumnsXml->tableColumn as $tableColumn) {
+            $column = $table->getColumnByOffset($offset++);
+
+            if ($table->getShowTotalsRow()) {
+                if ($tableColumn['totalsRowLabel']) {
+                    $column->setTotalsRowLabel((string) $tableColumn['totalsRowLabel']);
+                }
+
+                if ($tableColumn['totalsRowFunction']) {
+                    $column->setTotalsRowFunction((string) $tableColumn['totalsRowFunction']);
+                }
+            }
+
+            if ($tableColumn->calculatedColumnFormula) {
+                $column->setColumnFormula((string) $tableColumn->calculatedColumnFormula);
+            }
+        }
+    }
+
+    /**
+     * Reads TableStyle from xml.
+     */
+    private function readTableStyle(Table $table, SimpleXMLElement $tableStyleInfoXml): void
+    {
+        $tableStyle = new TableStyle();
+        $tableStyle->setTheme((string) $tableStyleInfoXml['name']);
+        $tableStyle->setShowRowStripes((string) $tableStyleInfoXml['showRowStripes'] === '1');
+        $tableStyle->setShowColumnStripes((string) $tableStyleInfoXml['showColumnStripes'] === '1');
+        $tableStyle->setShowFirstColumn((string) $tableStyleInfoXml['showFirstColumn'] === '1');
+        $tableStyle->setShowLastColumn((string) $tableStyleInfoXml['showLastColumn'] === '1');
+        $table->setStyle($tableStyle);
+    }
+}

+ 153 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php

@@ -0,0 +1,153 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use SimpleXMLElement;
+
+class WorkbookView
+{
+    /**
+     * @var Spreadsheet
+     */
+    private $spreadsheet;
+
+    public function __construct(Spreadsheet $spreadsheet)
+    {
+        $this->spreadsheet = $spreadsheet;
+    }
+
+    /**
+     * @param mixed $mainNS
+     */
+    public function viewSettings(SimpleXMLElement $xmlWorkbook, $mainNS, array $mapSheetId, bool $readDataOnly): void
+    {
+        if ($this->spreadsheet->getSheetCount() == 0) {
+            $this->spreadsheet->createSheet();
+        }
+        // Default active sheet index to the first loaded worksheet from the file
+        $this->spreadsheet->setActiveSheetIndex(0);
+
+        $workbookView = $xmlWorkbook->children($mainNS)->bookViews->workbookView;
+        if ($readDataOnly !== true && !empty($workbookView)) {
+            $workbookViewAttributes = self::testSimpleXml(self::getAttributes($workbookView));
+            // active sheet index
+            $activeTab = (int) $workbookViewAttributes->activeTab; // refers to old sheet index
+            // keep active sheet index if sheet is still loaded, else first sheet is set as the active worksheet
+            if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) {
+                $this->spreadsheet->setActiveSheetIndex($mapSheetId[$activeTab]);
+            }
+
+            $this->horizontalScroll($workbookViewAttributes);
+            $this->verticalScroll($workbookViewAttributes);
+            $this->sheetTabs($workbookViewAttributes);
+            $this->minimized($workbookViewAttributes);
+            $this->autoFilterDateGrouping($workbookViewAttributes);
+            $this->firstSheet($workbookViewAttributes);
+            $this->visibility($workbookViewAttributes);
+            $this->tabRatio($workbookViewAttributes);
+        }
+    }
+
+    /**
+     * @param mixed $value
+     */
+    public static function testSimpleXml($value): SimpleXMLElement
+    {
+        return ($value instanceof SimpleXMLElement)
+            ? $value
+            : new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><root></root>');
+    }
+
+    public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement
+    {
+        return self::testSimpleXml($value === null ? $value : $value->attributes($ns));
+    }
+
+    /**
+     * Convert an 'xsd:boolean' XML value to a PHP boolean value.
+     * A valid 'xsd:boolean' XML value can be one of the following
+     * four values: 'true', 'false', '1', '0'.  It is case sensitive.
+     *
+     * Note that just doing '(bool) $xsdBoolean' is not safe,
+     * since '(bool) "false"' returns true.
+     *
+     * @see https://www.w3.org/TR/xmlschema11-2/#boolean
+     *
+     * @param string $xsdBoolean An XML string value of type 'xsd:boolean'
+     *
+     * @return bool  Boolean value
+     */
+    private function castXsdBooleanToBool(string $xsdBoolean): bool
+    {
+        if ($xsdBoolean === 'false') {
+            return false;
+        }
+
+        return (bool) $xsdBoolean;
+    }
+
+    private function horizontalScroll(SimpleXMLElement $workbookViewAttributes): void
+    {
+        if (isset($workbookViewAttributes->showHorizontalScroll)) {
+            $showHorizontalScroll = (string) $workbookViewAttributes->showHorizontalScroll;
+            $this->spreadsheet->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll));
+        }
+    }
+
+    private function verticalScroll(SimpleXMLElement $workbookViewAttributes): void
+    {
+        if (isset($workbookViewAttributes->showVerticalScroll)) {
+            $showVerticalScroll = (string) $workbookViewAttributes->showVerticalScroll;
+            $this->spreadsheet->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll));
+        }
+    }
+
+    private function sheetTabs(SimpleXMLElement $workbookViewAttributes): void
+    {
+        if (isset($workbookViewAttributes->showSheetTabs)) {
+            $showSheetTabs = (string) $workbookViewAttributes->showSheetTabs;
+            $this->spreadsheet->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs));
+        }
+    }
+
+    private function minimized(SimpleXMLElement $workbookViewAttributes): void
+    {
+        if (isset($workbookViewAttributes->minimized)) {
+            $minimized = (string) $workbookViewAttributes->minimized;
+            $this->spreadsheet->setMinimized($this->castXsdBooleanToBool($minimized));
+        }
+    }
+
+    private function autoFilterDateGrouping(SimpleXMLElement $workbookViewAttributes): void
+    {
+        if (isset($workbookViewAttributes->autoFilterDateGrouping)) {
+            $autoFilterDateGrouping = (string) $workbookViewAttributes->autoFilterDateGrouping;
+            $this->spreadsheet->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping));
+        }
+    }
+
+    private function firstSheet(SimpleXMLElement $workbookViewAttributes): void
+    {
+        if (isset($workbookViewAttributes->firstSheet)) {
+            $firstSheet = (string) $workbookViewAttributes->firstSheet;
+            $this->spreadsheet->setFirstSheetIndex((int) $firstSheet);
+        }
+    }
+
+    private function visibility(SimpleXMLElement $workbookViewAttributes): void
+    {
+        if (isset($workbookViewAttributes->visibility)) {
+            $visibility = (string) $workbookViewAttributes->visibility;
+            $this->spreadsheet->setVisibility($visibility);
+        }
+    }
+
+    private function tabRatio(SimpleXMLElement $workbookViewAttributes): void
+    {
+        if (isset($workbookViewAttributes->tabRatio)) {
+            $tabRatio = (string) $workbookViewAttributes->tabRatio;
+            $this->spreadsheet->setTabRatio((int) $tabRatio);
+        }
+    }
+}

+ 177 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php

@@ -0,0 +1,177 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
+
+use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
+use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use SimpleXMLElement;
+
+class DataValidations
+{
+    private const OPERATOR_MAPPINGS = [
+        'between' => 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);
+            }
+        }
+    }
+}

+ 313 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php

@@ -0,0 +1,313 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class CellMatcher
+{
+    public const COMPARISON_OPERATORS = [
+        Conditional::OPERATOR_EQUAL => '=',
+        Conditional::OPERATOR_GREATERTHAN => '>',
+        Conditional::OPERATOR_GREATERTHANOREQUAL => '>=',
+        Conditional::OPERATOR_LESSTHAN => '<',
+        Conditional::OPERATOR_LESSTHANOREQUAL => '<=',
+        Conditional::OPERATOR_NOTEQUAL => '<>',
+    ];
+
+    public const COMPARISON_RANGE_OPERATORS = [
+        Conditional::OPERATOR_BETWEEN => 'IF(AND(A1>=%s,A1<=%s),TRUE,FALSE)',
+        Conditional::OPERATOR_NOTBETWEEN => 'IF(AND(A1>=%s,A1<=%s),FALSE,TRUE)',
+    ];
+
+    public const COMPARISON_DUPLICATES_OPERATORS = [
+        Conditional::CONDITION_DUPLICATES => "COUNTIF('%s'!%s,%s)>1",
+        Conditional::CONDITION_UNIQUE => "COUNTIF('%s'!%s,%s)=1",
+    ];
+
+    /**
+     * @var Cell
+     */
+    protected $cell;
+
+    /**
+     * @var int
+     */
+    protected $cellRow;
+
+    /**
+     * @var Worksheet
+     */
+    protected $worksheet;
+
+    /**
+     * @var int
+     */
+    protected $cellColumn;
+
+    /**
+     * @var string
+     */
+    protected $conditionalRange;
+
+    /**
+     * @var string
+     */
+    protected $referenceCell;
+
+    /**
+     * @var int
+     */
+    protected $referenceRow;
+
+    /**
+     * @var int
+     */
+    protected $referenceColumn;
+
+    /**
+     * @var Calculation
+     */
+    protected $engine;
+
+    public function __construct(Cell $cell, string $conditionalRange)
+    {
+        $this->cell = $cell;
+        $this->worksheet = $cell->getWorksheet();
+        [$this->cellColumn, $this->cellRow] = Coordinate::indexesFromString($this->cell->getCoordinate());
+        $this->setReferenceCellForExpressions($conditionalRange);
+
+        $this->engine = Calculation::getInstance($this->worksheet->getParent());
+    }
+
+    protected function setReferenceCellForExpressions(string $conditionalRange): void
+    {
+        $conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($conditionalRange)));
+        [$this->referenceCell] = $conditionalRange[0];
+
+        [$this->referenceColumn, $this->referenceRow] = Coordinate::indexesFromString($this->referenceCell);
+
+        // Convert our conditional range to an absolute conditional range, so it can be used  "pinned" in formulae
+        $rangeSets = [];
+        foreach ($conditionalRange as $rangeSet) {
+            $absoluteRangeSet = array_map(
+                [Coordinate::class, 'absoluteCoordinate'],
+                $rangeSet
+            );
+            $rangeSets[] = implode(':', $absoluteRangeSet);
+        }
+        $this->conditionalRange = implode(',', $rangeSets);
+    }
+
+    public function evaluateConditional(Conditional $conditional): bool
+    {
+        // Some calculations may modify the stored cell; so reset it before every evaluation.
+        $cellColumn = Coordinate::stringFromColumnIndex($this->cellColumn);
+        $cellAddress = "{$cellColumn}{$this->cellRow}";
+        $this->cell = $this->worksheet->getCell($cellAddress);
+
+        switch ($conditional->getConditionType()) {
+            case Conditional::CONDITION_CELLIS:
+                return $this->processOperatorComparison($conditional);
+            case Conditional::CONDITION_DUPLICATES:
+            case Conditional::CONDITION_UNIQUE:
+                return $this->processDuplicatesComparison($conditional);
+            case Conditional::CONDITION_CONTAINSTEXT:
+                // Expression is NOT(ISERROR(SEARCH("<TEXT>",<Cell Reference>)))
+            case Conditional::CONDITION_NOTCONTAINSTEXT:
+                // Expression is ISERROR(SEARCH("<TEXT>",<Cell Reference>))
+            case Conditional::CONDITION_BEGINSWITH:
+                // Expression is LEFT(<Cell Reference>,LEN("<TEXT>"))="<TEXT>"
+            case Conditional::CONDITION_ENDSWITH:
+                // Expression is RIGHT(<Cell Reference>,LEN("<TEXT>"))="<TEXT>"
+            case Conditional::CONDITION_CONTAINSBLANKS:
+                // Expression is LEN(TRIM(<Cell Reference>))=0
+            case Conditional::CONDITION_NOTCONTAINSBLANKS:
+                // Expression is LEN(TRIM(<Cell Reference>))>0
+            case Conditional::CONDITION_CONTAINSERRORS:
+                // Expression is ISERROR(<Cell Reference>)
+            case Conditional::CONDITION_NOTCONTAINSERRORS:
+                // Expression is NOT(ISERROR(<Cell Reference>))
+            case Conditional::CONDITION_TIMEPERIOD:
+                // Expression varies, depending on specified timePeriod value, e.g.
+                // Yesterday FLOOR(<Cell Reference>,1)=TODAY()-1
+                // Today FLOOR(<Cell Reference>,1)=TODAY()
+                // Tomorrow FLOOR(<Cell Reference>,1)=TODAY()+1
+                // Last 7 Days AND(TODAY()-FLOOR(<Cell Reference>,1)<=6,FLOOR(<Cell Reference>,1)<=TODAY())
+            case Conditional::CONDITION_EXPRESSION:
+                return $this->processExpression($conditional);
+        }
+
+        return false;
+    }
+
+    /**
+     * @param mixed $value
+     *
+     * @return float|int|string
+     */
+    protected function wrapValue($value)
+    {
+        if (!is_numeric($value)) {
+            if (is_bool($value)) {
+                return $value ? 'TRUE' : 'FALSE';
+            } elseif ($value === null) {
+                return 'NULL';
+            }
+
+            return '"' . $value . '"';
+        }
+
+        return $value;
+    }
+
+    /**
+     * @return float|int|string
+     */
+    protected function wrapCellValue()
+    {
+        return $this->wrapValue($this->cell->getCalculatedValue());
+    }
+
+    /**
+     * @return float|int|string
+     */
+    protected function conditionCellAdjustment(array $matches)
+    {
+        $column = $matches[6];
+        $row = $matches[7];
+
+        if (strpos($column, '$') === false) {
+            $column = Coordinate::columnIndexFromString($column);
+            $column += $this->cellColumn - $this->referenceColumn;
+            $column = Coordinate::stringFromColumnIndex($column);
+        }
+
+        if (strpos($row, '$') === false) {
+            $row += $this->cellRow - $this->referenceRow;
+        }
+
+        if (!empty($matches[4])) {
+            $worksheet = $this->worksheet->getParentOrThrow()->getSheetByName(trim($matches[4], "'"));
+            if ($worksheet === null) {
+                return $this->wrapValue(null);
+            }
+
+            return $this->wrapValue(
+                $worksheet
+                    ->getCell(str_replace('$', '', "{$column}{$row}"))
+                    ->getCalculatedValue()
+            );
+        }
+
+        return $this->wrapValue(
+            $this->worksheet
+                ->getCell(str_replace('$', '', "{$column}{$row}"))
+                ->getCalculatedValue()
+        );
+    }
+
+    protected function cellConditionCheck(string $condition): string
+    {
+        $splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition);
+        $i = false;
+        foreach ($splitCondition as &$value) {
+            //    Only count/replace in alternating array entries (ie. not in quoted strings)
+            $i = $i === false;
+            if ($i) {
+                $value = (string) preg_replace_callback(
+                    '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
+                    [$this, 'conditionCellAdjustment'],
+                    $value
+                );
+            }
+        }
+        unset($value);
+        //    Then rebuild the condition string to return it
+        return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition);
+    }
+
+    protected function adjustConditionsForCellReferences(array $conditions): array
+    {
+        return array_map(
+            [$this, 'cellConditionCheck'],
+            $conditions
+        );
+    }
+
+    protected function processOperatorComparison(Conditional $conditional): bool
+    {
+        if (array_key_exists($conditional->getOperatorType(), self::COMPARISON_RANGE_OPERATORS)) {
+            return $this->processRangeOperator($conditional);
+        }
+
+        $operator = self::COMPARISON_OPERATORS[$conditional->getOperatorType()];
+        $conditions = $this->adjustConditionsForCellReferences($conditional->getConditions());
+        $expression = sprintf('%s%s%s', (string) $this->wrapCellValue(), $operator, (string) array_pop($conditions));
+
+        return $this->evaluateExpression($expression);
+    }
+
+    protected function processRangeOperator(Conditional $conditional): bool
+    {
+        $conditions = $this->adjustConditionsForCellReferences($conditional->getConditions());
+        sort($conditions);
+        $expression = sprintf(
+            (string) preg_replace(
+                '/\bA1\b/i',
+                (string) $this->wrapCellValue(),
+                self::COMPARISON_RANGE_OPERATORS[$conditional->getOperatorType()]
+            ),
+            ...$conditions
+        );
+
+        return $this->evaluateExpression($expression);
+    }
+
+    protected function processDuplicatesComparison(Conditional $conditional): bool
+    {
+        $worksheetName = $this->cell->getWorksheet()->getTitle();
+
+        $expression = sprintf(
+            self::COMPARISON_DUPLICATES_OPERATORS[$conditional->getConditionType()],
+            $worksheetName,
+            $this->conditionalRange,
+            $this->cellConditionCheck($this->cell->getCalculatedValue())
+        );
+
+        return $this->evaluateExpression($expression);
+    }
+
+    protected function processExpression(Conditional $conditional): bool
+    {
+        $conditions = $this->adjustConditionsForCellReferences($conditional->getConditions());
+        $expression = array_pop($conditions);
+
+        $expression = (string) preg_replace(
+            '/\b' . $this->referenceCell . '\b/i',
+            (string) $this->wrapCellValue(),
+            $expression
+        );
+
+        return $this->evaluateExpression($expression);
+    }
+
+    protected function evaluateExpression(string $expression): bool
+    {
+        $expression = "={$expression}";
+
+        try {
+            $this->engine->flushInstance();
+            $result = (bool) $this->engine->calculateFormula($expression);
+        } catch (Exception $e) {
+            return false;
+        }
+
+        return $result;
+    }
+}

+ 45 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
+
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+
+class CellStyleAssessor
+{
+    /**
+     * @var CellMatcher
+     */
+    protected $cellMatcher;
+
+    /**
+     * @var StyleMerger
+     */
+    protected $styleMerger;
+
+    public function __construct(Cell $cell, string $conditionalRange)
+    {
+        $this->cellMatcher = new CellMatcher($cell, $conditionalRange);
+        $this->styleMerger = new StyleMerger($cell->getStyle());
+    }
+
+    /**
+     * @param Conditional[] $conditionalStyles
+     */
+    public function matchConditions(array $conditionalStyles = []): Style
+    {
+        foreach ($conditionalStyles as $conditional) {
+            /** @var Conditional $conditional */
+            if ($this->cellMatcher->evaluateConditional($conditional) === true) {
+                // Merging the conditional style into the base style goes in here
+                $this->styleMerger->mergeStyle($conditional->getStyle());
+                if ($conditional->getStopIfTrue() === true) {
+                    break;
+                }
+            }
+        }
+
+        return $this->styleMerger->getStyle();
+    }
+}

+ 118 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php

@@ -0,0 +1,118 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
+
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Borders;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+
+class StyleMerger
+{
+    /**
+     * @var Style
+     */
+    protected $baseStyle;
+
+    public function __construct(Style $baseStyle)
+    {
+        $this->baseStyle = $baseStyle;
+    }
+
+    public function getStyle(): Style
+    {
+        return $this->baseStyle;
+    }
+
+    public function mergeStyle(Style $style): void
+    {
+        if ($style->getNumberFormat() !== null && $style->getNumberFormat()->getFormatCode() !== null) {
+            $this->baseStyle->getNumberFormat()->setFormatCode($style->getNumberFormat()->getFormatCode());
+        }
+
+        if ($style->getFont() !== null) {
+            $this->mergeFontStyle($this->baseStyle->getFont(), $style->getFont());
+        }
+
+        if ($style->getFill() !== null) {
+            $this->mergeFillStyle($this->baseStyle->getFill(), $style->getFill());
+        }
+
+        if ($style->getBorders() !== null) {
+            $this->mergeBordersStyle($this->baseStyle->getBorders(), $style->getBorders());
+        }
+    }
+
+    protected function mergeFontStyle(Font $baseFontStyle, Font $fontStyle): void
+    {
+        if ($fontStyle->getBold() !== null) {
+            $baseFontStyle->setBold($fontStyle->getBold());
+        }
+
+        if ($fontStyle->getItalic() !== null) {
+            $baseFontStyle->setItalic($fontStyle->getItalic());
+        }
+
+        if ($fontStyle->getStrikethrough() !== null) {
+            $baseFontStyle->setStrikethrough($fontStyle->getStrikethrough());
+        }
+
+        if ($fontStyle->getUnderline() !== null) {
+            $baseFontStyle->setUnderline($fontStyle->getUnderline());
+        }
+
+        if ($fontStyle->getColor() !== null && $fontStyle->getColor()->getARGB() !== null) {
+            $baseFontStyle->setColor($fontStyle->getColor());
+        }
+    }
+
+    protected function mergeFillStyle(Fill $baseFillStyle, Fill $fillStyle): void
+    {
+        if ($fillStyle->getFillType() !== null) {
+            $baseFillStyle->setFillType($fillStyle->getFillType());
+        }
+
+        //if ($fillStyle->getRotation() !== null) {
+        $baseFillStyle->setRotation($fillStyle->getRotation());
+        //}
+
+        if ($fillStyle->getStartColor() !== null && $fillStyle->getStartColor()->getARGB() !== null) {
+            $baseFillStyle->setStartColor($fillStyle->getStartColor());
+        }
+
+        if ($fillStyle->getEndColor() !== null && $fillStyle->getEndColor()->getARGB() !== null) {
+            $baseFillStyle->setEndColor($fillStyle->getEndColor());
+        }
+    }
+
+    protected function mergeBordersStyle(Borders $baseBordersStyle, Borders $bordersStyle): void
+    {
+        if ($bordersStyle->getTop() !== null) {
+            $this->mergeBorderStyle($baseBordersStyle->getTop(), $bordersStyle->getTop());
+        }
+
+        if ($bordersStyle->getBottom() !== null) {
+            $this->mergeBorderStyle($baseBordersStyle->getBottom(), $bordersStyle->getBottom());
+        }
+
+        if ($bordersStyle->getLeft() !== null) {
+            $this->mergeBorderStyle($baseBordersStyle->getLeft(), $bordersStyle->getLeft());
+        }
+
+        if ($bordersStyle->getRight() !== null) {
+            $this->mergeBorderStyle($baseBordersStyle->getRight(), $bordersStyle->getRight());
+        }
+    }
+
+    protected function mergeBorderStyle(Border $baseBorderStyle, Border $borderStyle): void
+    {
+        //if ($borderStyle->getBorderStyle() !== null) {
+        $baseBorderStyle->setBorderStyle($borderStyle->getBorderStyle());
+        //}
+
+        if ($borderStyle->getColor() !== null && $borderStyle->getColor()->getARGB() !== null) {
+            $baseBorderStyle->setColor($borderStyle->getColor());
+        }
+    }
+}

+ 95 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\WizardInterface;
+
+class Wizard
+{
+    public const CELL_VALUE = 'cellValue';
+    public const TEXT_VALUE = 'textValue';
+    public const BLANKS = Conditional::CONDITION_CONTAINSBLANKS;
+    public const NOT_BLANKS = Conditional::CONDITION_NOTCONTAINSBLANKS;
+    public const ERRORS = Conditional::CONDITION_CONTAINSERRORS;
+    public const NOT_ERRORS = Conditional::CONDITION_NOTCONTAINSERRORS;
+    public const EXPRESSION = Conditional::CONDITION_EXPRESSION;
+    public const FORMULA = Conditional::CONDITION_EXPRESSION;
+    public const DATES_OCCURRING = 'DateValue';
+    public const DUPLICATES = Conditional::CONDITION_DUPLICATES;
+    public const UNIQUE = Conditional::CONDITION_UNIQUE;
+
+    public const VALUE_TYPE_LITERAL = 'value';
+    public const VALUE_TYPE_CELL = 'cell';
+    public const VALUE_TYPE_FORMULA = 'formula';
+
+    /**
+     * @var string
+     */
+    protected $cellRange;
+
+    public function __construct(string $cellRange)
+    {
+        $this->cellRange = $cellRange;
+    }
+
+    public function newRule(string $ruleType): WizardInterface
+    {
+        switch ($ruleType) {
+            case self::CELL_VALUE:
+                return new Wizard\CellValue($this->cellRange);
+            case self::TEXT_VALUE:
+                return new Wizard\TextValue($this->cellRange);
+            case self::BLANKS:
+                return new Wizard\Blanks($this->cellRange, true);
+            case self::NOT_BLANKS:
+                return new Wizard\Blanks($this->cellRange, false);
+            case self::ERRORS:
+                return new Wizard\Errors($this->cellRange, true);
+            case self::NOT_ERRORS:
+                return new Wizard\Errors($this->cellRange, false);
+            case self::EXPRESSION:
+            case self::FORMULA:
+                return new Wizard\Expression($this->cellRange);
+            case self::DATES_OCCURRING:
+                return new Wizard\DateValue($this->cellRange);
+            case self::DUPLICATES:
+                return new Wizard\Duplicates($this->cellRange, false);
+            case self::UNIQUE:
+                return new Wizard\Duplicates($this->cellRange, true);
+            default:
+                throw new Exception('No wizard exists for this CF rule type');
+        }
+    }
+
+    public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
+    {
+        $conditionalType = $conditional->getConditionType();
+
+        switch ($conditionalType) {
+            case Conditional::CONDITION_CELLIS:
+                return Wizard\CellValue::fromConditional($conditional, $cellRange);
+            case Conditional::CONDITION_CONTAINSTEXT:
+            case Conditional::CONDITION_NOTCONTAINSTEXT:
+            case Conditional::CONDITION_BEGINSWITH:
+            case Conditional::CONDITION_ENDSWITH:
+                return Wizard\TextValue::fromConditional($conditional, $cellRange);
+            case Conditional::CONDITION_CONTAINSBLANKS:
+            case Conditional::CONDITION_NOTCONTAINSBLANKS:
+                return Wizard\Blanks::fromConditional($conditional, $cellRange);
+            case Conditional::CONDITION_CONTAINSERRORS:
+            case Conditional::CONDITION_NOTCONTAINSERRORS:
+                return Wizard\Errors::fromConditional($conditional, $cellRange);
+            case Conditional::CONDITION_TIMEPERIOD:
+                return Wizard\DateValue::fromConditional($conditional, $cellRange);
+            case Conditional::CONDITION_EXPRESSION:
+                return Wizard\Expression::fromConditional($conditional, $cellRange);
+            case Conditional::CONDITION_DUPLICATES:
+            case Conditional::CONDITION_UNIQUE:
+                return Wizard\Duplicates::fromConditional($conditional, $cellRange);
+            default:
+                throw new Exception('No wizard exists for this CF rule type');
+        }
+    }
+}

+ 99 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php

@@ -0,0 +1,99 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+/**
+ * @method Blanks notBlank()
+ * @method Blanks notEmpty()
+ * @method Blanks isBlank()
+ * @method Blanks isEmpty()
+ */
+class Blanks extends WizardAbstract implements WizardInterface
+{
+    protected const OPERATORS = [
+        'notBlank' => false,
+        'isBlank' => true,
+        'notEmpty' => false,
+        'empty' => true,
+    ];
+
+    protected const EXPRESSIONS = [
+        Wizard::NOT_BLANKS => 'LEN(TRIM(%s))>0',
+        Wizard::BLANKS => 'LEN(TRIM(%s))=0',
+    ];
+
+    /**
+     * @var bool
+     */
+    protected $inverse;
+
+    public function __construct(string $cellRange, bool $inverse = false)
+    {
+        parent::__construct($cellRange);
+        $this->inverse = $inverse;
+    }
+
+    protected function inverse(bool $inverse): void
+    {
+        $this->inverse = $inverse;
+    }
+
+    protected function getExpression(): void
+    {
+        $this->expression = sprintf(
+            self::EXPRESSIONS[$this->inverse ? Wizard::BLANKS : Wizard::NOT_BLANKS],
+            $this->referenceCell
+        );
+    }
+
+    public function getConditional(): Conditional
+    {
+        $this->getExpression();
+
+        $conditional = new Conditional();
+        $conditional->setConditionType(
+            $this->inverse ? Conditional::CONDITION_CONTAINSBLANKS : Conditional::CONDITION_NOTCONTAINSBLANKS
+        );
+        $conditional->setConditions([$this->expression]);
+        $conditional->setStyle($this->getStyle());
+        $conditional->setStopIfTrue($this->getStopIfTrue());
+
+        return $conditional;
+    }
+
+    public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
+    {
+        if (
+            $conditional->getConditionType() !== Conditional::CONDITION_CONTAINSBLANKS &&
+            $conditional->getConditionType() !== Conditional::CONDITION_NOTCONTAINSBLANKS
+        ) {
+            throw new Exception('Conditional is not a Blanks CF Rule conditional');
+        }
+
+        $wizard = new self($cellRange);
+        $wizard->style = $conditional->getStyle();
+        $wizard->stopIfTrue = $conditional->getStopIfTrue();
+        $wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_CONTAINSBLANKS;
+
+        return $wizard;
+    }
+
+    /**
+     * @param string $methodName
+     * @param mixed[] $arguments
+     */
+    public function __call($methodName, $arguments): self
+    {
+        if (!array_key_exists($methodName, self::OPERATORS)) {
+            throw new Exception('Invalid Operation for Blanks CF Rule Wizard');
+        }
+
+        $this->inverse(self::OPERATORS[$methodName]);
+
+        return $this;
+    }
+}

+ 200 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php

@@ -0,0 +1,200 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellMatcher;
+use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+/**
+ * @method CellValue equals($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method CellValue notEquals($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method CellValue greaterThan($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method CellValue greaterThanOrEqual($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method CellValue lessThan($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method CellValue lessThanOrEqual($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method CellValue between($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method CellValue notBetween($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method CellValue and($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ */
+class CellValue extends WizardAbstract implements WizardInterface
+{
+    protected const MAGIC_OPERATIONS = [
+        'equals' => Conditional::OPERATOR_EQUAL,
+        'notEquals' => Conditional::OPERATOR_NOTEQUAL,
+        'greaterThan' => Conditional::OPERATOR_GREATERTHAN,
+        'greaterThanOrEqual' => Conditional::OPERATOR_GREATERTHANOREQUAL,
+        'lessThan' => Conditional::OPERATOR_LESSTHAN,
+        'lessThanOrEqual' => Conditional::OPERATOR_LESSTHANOREQUAL,
+        'between' => Conditional::OPERATOR_BETWEEN,
+        'notBetween' => Conditional::OPERATOR_NOTBETWEEN,
+    ];
+
+    protected const SINGLE_OPERATORS = CellMatcher::COMPARISON_OPERATORS;
+
+    protected const RANGE_OPERATORS = CellMatcher::COMPARISON_RANGE_OPERATORS;
+
+    /** @var string */
+    protected $operator = Conditional::OPERATOR_EQUAL;
+
+    /** @var array */
+    protected $operand = [0];
+
+    /**
+     * @var string[]
+     */
+    protected $operandValueType = [];
+
+    public function __construct(string $cellRange)
+    {
+        parent::__construct($cellRange);
+    }
+
+    protected function operator(string $operator): void
+    {
+        if ((!isset(self::SINGLE_OPERATORS[$operator])) && (!isset(self::RANGE_OPERATORS[$operator]))) {
+            throw new Exception('Invalid Operator for Cell Value CF Rule Wizard');
+        }
+
+        $this->operator = $operator;
+    }
+
+    /**
+     * @param mixed $operand
+     */
+    protected function operand(int $index, $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): void
+    {
+        if (is_string($operand)) {
+            $operand = $this->validateOperand($operand, $operandValueType);
+        }
+
+        $this->operand[$index] = $operand;
+        $this->operandValueType[$index] = $operandValueType;
+    }
+
+    /**
+     * @param mixed $value
+     *
+     * @return float|int|string
+     */
+    protected function wrapValue($value, string $operandValueType)
+    {
+        if (!is_numeric($value) && !is_bool($value) && null !== $value) {
+            if ($operandValueType === Wizard::VALUE_TYPE_LITERAL) {
+                return '"' . str_replace('"', '""', $value) . '"';
+            }
+
+            return $this->cellConditionCheck($value);
+        }
+
+        if (null === $value) {
+            $value = 'NULL';
+        } elseif (is_bool($value)) {
+            $value = $value ? 'TRUE' : 'FALSE';
+        }
+
+        return $value;
+    }
+
+    public function getConditional(): Conditional
+    {
+        if (!isset(self::RANGE_OPERATORS[$this->operator])) {
+            unset($this->operand[1], $this->operandValueType[1]);
+        }
+        $values = array_map([$this, 'wrapValue'], $this->operand, $this->operandValueType);
+
+        $conditional = new Conditional();
+        $conditional->setConditionType(Conditional::CONDITION_CELLIS);
+        $conditional->setOperatorType($this->operator);
+        $conditional->setConditions($values);
+        $conditional->setStyle($this->getStyle());
+        $conditional->setStopIfTrue($this->getStopIfTrue());
+
+        return $conditional;
+    }
+
+    protected static function unwrapString(string $condition): string
+    {
+        if ((strpos($condition, '"') === 0) && (strpos(strrev($condition), '"') === 0)) {
+            $condition = substr($condition, 1, -1);
+        }
+
+        return str_replace('""', '"', $condition);
+    }
+
+    public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
+    {
+        if ($conditional->getConditionType() !== Conditional::CONDITION_CELLIS) {
+            throw new Exception('Conditional is not a Cell Value CF Rule conditional');
+        }
+
+        $wizard = new self($cellRange);
+        $wizard->style = $conditional->getStyle();
+        $wizard->stopIfTrue = $conditional->getStopIfTrue();
+
+        $wizard->operator = $conditional->getOperatorType();
+        $conditions = $conditional->getConditions();
+        foreach ($conditions as $index => $condition) {
+            // Best-guess to try and identify if the text is a string literal, a cell reference or a formula?
+            $operandValueType = Wizard::VALUE_TYPE_LITERAL;
+            if (is_string($condition)) {
+                if (Calculation::keyInExcelConstants($condition)) {
+                    $condition = Calculation::getExcelConstants($condition);
+                } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) {
+                    $operandValueType = Wizard::VALUE_TYPE_CELL;
+                    $condition = self::reverseAdjustCellRef($condition, $cellRange);
+                } elseif (
+                    preg_match('/\(\)/', $condition) ||
+                    preg_match('/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', $condition)
+                ) {
+                    $operandValueType = Wizard::VALUE_TYPE_FORMULA;
+                    $condition = self::reverseAdjustCellRef($condition, $cellRange);
+                } else {
+                    $condition = self::unwrapString($condition);
+                }
+            }
+            $wizard->operand($index, $condition, $operandValueType);
+        }
+
+        return $wizard;
+    }
+
+    /**
+     * @param string $methodName
+     * @param mixed[] $arguments
+     */
+    public function __call($methodName, $arguments): self
+    {
+        if (!isset(self::MAGIC_OPERATIONS[$methodName]) && $methodName !== 'and') {
+            throw new Exception('Invalid Operator for Cell Value CF Rule Wizard');
+        }
+
+        if ($methodName === 'and') {
+            if (!isset(self::RANGE_OPERATORS[$this->operator])) {
+                throw new Exception('AND Value is only appropriate for range operators');
+            }
+
+            // Scrutinizer ignores its own suggested workaround.
+            //$this->operand(1, /** @scrutinizer ignore-type */ ...$arguments);
+            if (count($arguments) < 2) {
+                $this->operand(1, $arguments[0]);
+            } else {
+                $this->operand(1, $arguments[0], $arguments[1]);
+            }
+
+            return $this;
+        }
+
+        $this->operator(self::MAGIC_OPERATIONS[$methodName]);
+        //$this->operand(0, ...$arguments);
+        if (count($arguments) < 2) {
+            $this->operand(0, $arguments[0]);
+        } else {
+            $this->operand(0, $arguments[0], $arguments[1]);
+        }
+
+        return $this;
+    }
+}

+ 111 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php

@@ -0,0 +1,111 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+
+/**
+ * @method DateValue yesterday()
+ * @method DateValue today()
+ * @method DateValue tomorrow()
+ * @method DateValue lastSevenDays()
+ * @method DateValue lastWeek()
+ * @method DateValue thisWeek()
+ * @method DateValue nextWeek()
+ * @method DateValue lastMonth()
+ * @method DateValue thisMonth()
+ * @method DateValue nextMonth()
+ */
+class DateValue extends WizardAbstract implements WizardInterface
+{
+    protected const MAGIC_OPERATIONS = [
+        'yesterday' => Conditional::TIMEPERIOD_YESTERDAY,
+        'today' => Conditional::TIMEPERIOD_TODAY,
+        'tomorrow' => Conditional::TIMEPERIOD_TOMORROW,
+        'lastSevenDays' => Conditional::TIMEPERIOD_LAST_7_DAYS,
+        'last7Days' => Conditional::TIMEPERIOD_LAST_7_DAYS,
+        'lastWeek' => Conditional::TIMEPERIOD_LAST_WEEK,
+        'thisWeek' => Conditional::TIMEPERIOD_THIS_WEEK,
+        'nextWeek' => Conditional::TIMEPERIOD_NEXT_WEEK,
+        'lastMonth' => Conditional::TIMEPERIOD_LAST_MONTH,
+        'thisMonth' => Conditional::TIMEPERIOD_THIS_MONTH,
+        'nextMonth' => Conditional::TIMEPERIOD_NEXT_MONTH,
+    ];
+
+    protected const EXPRESSIONS = [
+        Conditional::TIMEPERIOD_YESTERDAY => 'FLOOR(%s,1)=TODAY()-1',
+        Conditional::TIMEPERIOD_TODAY => 'FLOOR(%s,1)=TODAY()',
+        Conditional::TIMEPERIOD_TOMORROW => 'FLOOR(%s,1)=TODAY()+1',
+        Conditional::TIMEPERIOD_LAST_7_DAYS => 'AND(TODAY()-FLOOR(%s,1)<=6,FLOOR(%s,1)<=TODAY())',
+        Conditional::TIMEPERIOD_LAST_WEEK => 'AND(TODAY()-ROUNDDOWN(%s,0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(%s,0)<(WEEKDAY(TODAY())+7))',
+        Conditional::TIMEPERIOD_THIS_WEEK => 'AND(TODAY()-ROUNDDOWN(%s,0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(%s,0)-TODAY()<=7-WEEKDAY(TODAY()))',
+        Conditional::TIMEPERIOD_NEXT_WEEK => 'AND(ROUNDDOWN(%s,0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(%s,0)-TODAY()<(15-WEEKDAY(TODAY())))',
+        Conditional::TIMEPERIOD_LAST_MONTH => 'AND(MONTH(%s)=MONTH(EDATE(TODAY(),0-1)),YEAR(%s)=YEAR(EDATE(TODAY(),0-1)))',
+        Conditional::TIMEPERIOD_THIS_MONTH => 'AND(MONTH(%s)=MONTH(TODAY()),YEAR(%s)=YEAR(TODAY()))',
+        Conditional::TIMEPERIOD_NEXT_MONTH => 'AND(MONTH(%s)=MONTH(EDATE(TODAY(),0+1)),YEAR(%s)=YEAR(EDATE(TODAY(),0+1)))',
+    ];
+
+    /** @var string */
+    protected $operator;
+
+    public function __construct(string $cellRange)
+    {
+        parent::__construct($cellRange);
+    }
+
+    protected function operator(string $operator): void
+    {
+        $this->operator = $operator;
+    }
+
+    protected function setExpression(): void
+    {
+        $referenceCount = substr_count(self::EXPRESSIONS[$this->operator], '%s');
+        $references = array_fill(0, $referenceCount, $this->referenceCell);
+        $this->expression = sprintf(self::EXPRESSIONS[$this->operator], ...$references);
+    }
+
+    public function getConditional(): Conditional
+    {
+        $this->setExpression();
+
+        $conditional = new Conditional();
+        $conditional->setConditionType(Conditional::CONDITION_TIMEPERIOD);
+        $conditional->setText($this->operator);
+        $conditional->setConditions([$this->expression]);
+        $conditional->setStyle($this->getStyle());
+        $conditional->setStopIfTrue($this->getStopIfTrue());
+
+        return $conditional;
+    }
+
+    public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
+    {
+        if ($conditional->getConditionType() !== Conditional::CONDITION_TIMEPERIOD) {
+            throw new Exception('Conditional is not a Date Value CF Rule conditional');
+        }
+
+        $wizard = new self($cellRange);
+        $wizard->style = $conditional->getStyle();
+        $wizard->stopIfTrue = $conditional->getStopIfTrue();
+        $wizard->operator = $conditional->getText();
+
+        return $wizard;
+    }
+
+    /**
+     * @param string $methodName
+     * @param mixed[] $arguments
+     */
+    public function __call($methodName, $arguments): self
+    {
+        if (!isset(self::MAGIC_OPERATIONS[$methodName])) {
+            throw new Exception('Invalid Operation for Date Value CF Rule Wizard');
+        }
+
+        $this->operator(self::MAGIC_OPERATIONS[$methodName]);
+
+        return $this;
+    }
+}

+ 78 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+
+/**
+ * @method Errors duplicates()
+ * @method Errors unique()
+ */
+class Duplicates extends WizardAbstract implements WizardInterface
+{
+    protected const OPERATORS = [
+        'duplicates' => false,
+        'unique' => true,
+    ];
+
+    /**
+     * @var bool
+     */
+    protected $inverse;
+
+    public function __construct(string $cellRange, bool $inverse = false)
+    {
+        parent::__construct($cellRange);
+        $this->inverse = $inverse;
+    }
+
+    protected function inverse(bool $inverse): void
+    {
+        $this->inverse = $inverse;
+    }
+
+    public function getConditional(): Conditional
+    {
+        $conditional = new Conditional();
+        $conditional->setConditionType(
+            $this->inverse ? Conditional::CONDITION_UNIQUE : Conditional::CONDITION_DUPLICATES
+        );
+        $conditional->setStyle($this->getStyle());
+        $conditional->setStopIfTrue($this->getStopIfTrue());
+
+        return $conditional;
+    }
+
+    public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
+    {
+        if (
+            $conditional->getConditionType() !== Conditional::CONDITION_DUPLICATES &&
+            $conditional->getConditionType() !== Conditional::CONDITION_UNIQUE
+        ) {
+            throw new Exception('Conditional is not a Duplicates CF Rule conditional');
+        }
+
+        $wizard = new self($cellRange);
+        $wizard->style = $conditional->getStyle();
+        $wizard->stopIfTrue = $conditional->getStopIfTrue();
+        $wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_UNIQUE;
+
+        return $wizard;
+    }
+
+    /**
+     * @param string $methodName
+     * @param mixed[] $arguments
+     */
+    public function __call($methodName, $arguments): self
+    {
+        if (!array_key_exists($methodName, self::OPERATORS)) {
+            throw new Exception('Invalid Operation for Errors CF Rule Wizard');
+        }
+
+        $this->inverse(self::OPERATORS[$methodName]);
+
+        return $this;
+    }
+}

+ 95 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+/**
+ * @method Errors notError()
+ * @method Errors isError()
+ */
+class Errors extends WizardAbstract implements WizardInterface
+{
+    protected const OPERATORS = [
+        'notError' => false,
+        'isError' => true,
+    ];
+
+    protected const EXPRESSIONS = [
+        Wizard::NOT_ERRORS => 'NOT(ISERROR(%s))',
+        Wizard::ERRORS => 'ISERROR(%s)',
+    ];
+
+    /**
+     * @var bool
+     */
+    protected $inverse;
+
+    public function __construct(string $cellRange, bool $inverse = false)
+    {
+        parent::__construct($cellRange);
+        $this->inverse = $inverse;
+    }
+
+    protected function inverse(bool $inverse): void
+    {
+        $this->inverse = $inverse;
+    }
+
+    protected function getExpression(): void
+    {
+        $this->expression = sprintf(
+            self::EXPRESSIONS[$this->inverse ? Wizard::ERRORS : Wizard::NOT_ERRORS],
+            $this->referenceCell
+        );
+    }
+
+    public function getConditional(): Conditional
+    {
+        $this->getExpression();
+
+        $conditional = new Conditional();
+        $conditional->setConditionType(
+            $this->inverse ? Conditional::CONDITION_CONTAINSERRORS : Conditional::CONDITION_NOTCONTAINSERRORS
+        );
+        $conditional->setConditions([$this->expression]);
+        $conditional->setStyle($this->getStyle());
+        $conditional->setStopIfTrue($this->getStopIfTrue());
+
+        return $conditional;
+    }
+
+    public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
+    {
+        if (
+            $conditional->getConditionType() !== Conditional::CONDITION_CONTAINSERRORS &&
+            $conditional->getConditionType() !== Conditional::CONDITION_NOTCONTAINSERRORS
+        ) {
+            throw new Exception('Conditional is not an Errors CF Rule conditional');
+        }
+
+        $wizard = new self($cellRange);
+        $wizard->style = $conditional->getStyle();
+        $wizard->stopIfTrue = $conditional->getStopIfTrue();
+        $wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_CONTAINSERRORS;
+
+        return $wizard;
+    }
+
+    /**
+     * @param string $methodName
+     * @param mixed[] $arguments
+     */
+    public function __call($methodName, $arguments): self
+    {
+        if (!array_key_exists($methodName, self::OPERATORS)) {
+            throw new Exception('Invalid Operation for Errors CF Rule Wizard');
+        }
+
+        $this->inverse(self::OPERATORS[$methodName]);
+
+        return $this;
+    }
+}

+ 75 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+/**
+ * @method Expression formula(string $expression)
+ */
+class Expression extends WizardAbstract implements WizardInterface
+{
+    /**
+     * @var string
+     */
+    protected $expression;
+
+    public function __construct(string $cellRange)
+    {
+        parent::__construct($cellRange);
+    }
+
+    public function expression(string $expression): self
+    {
+        $expression = $this->validateOperand($expression, Wizard::VALUE_TYPE_FORMULA);
+        $this->expression = $expression;
+
+        return $this;
+    }
+
+    public function getConditional(): Conditional
+    {
+        $expression = $this->adjustConditionsForCellReferences([$this->expression]);
+
+        $conditional = new Conditional();
+        $conditional->setConditionType(Conditional::CONDITION_EXPRESSION);
+        $conditional->setConditions($expression);
+        $conditional->setStyle($this->getStyle());
+        $conditional->setStopIfTrue($this->getStopIfTrue());
+
+        return $conditional;
+    }
+
+    public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
+    {
+        if ($conditional->getConditionType() !== Conditional::CONDITION_EXPRESSION) {
+            throw new Exception('Conditional is not an Expression CF Rule conditional');
+        }
+
+        $wizard = new self($cellRange);
+        $wizard->style = $conditional->getStyle();
+        $wizard->stopIfTrue = $conditional->getStopIfTrue();
+        $wizard->expression = self::reverseAdjustCellRef((string) ($conditional->getConditions()[0]), $cellRange);
+
+        return $wizard;
+    }
+
+    /**
+     * @param string $methodName
+     * @param mixed[] $arguments
+     */
+    public function __call($methodName, $arguments): self
+    {
+        if ($methodName !== 'formula') {
+            throw new Exception('Invalid Operation for Expression CF Rule Wizard');
+        }
+
+        // Scrutinizer ignores its own recommendation
+        //$this->expression(/** @scrutinizer ignore-type */ ...$arguments);
+        $this->expression($arguments[0]);
+
+        return $this;
+    }
+}

+ 164 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php

@@ -0,0 +1,164 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+/**
+ * @method TextValue contains(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method TextValue doesNotContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method TextValue doesntContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method TextValue beginsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method TextValue startsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ * @method TextValue endsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
+ */
+class TextValue extends WizardAbstract implements WizardInterface
+{
+    protected const MAGIC_OPERATIONS = [
+        'contains' => Conditional::OPERATOR_CONTAINSTEXT,
+        'doesntContain' => Conditional::OPERATOR_NOTCONTAINS,
+        'doesNotContain' => Conditional::OPERATOR_NOTCONTAINS,
+        'beginsWith' => Conditional::OPERATOR_BEGINSWITH,
+        'startsWith' => Conditional::OPERATOR_BEGINSWITH,
+        'endsWith' => Conditional::OPERATOR_ENDSWITH,
+    ];
+
+    protected const OPERATORS = [
+        Conditional::OPERATOR_CONTAINSTEXT => Conditional::CONDITION_CONTAINSTEXT,
+        Conditional::OPERATOR_NOTCONTAINS => Conditional::CONDITION_NOTCONTAINSTEXT,
+        Conditional::OPERATOR_BEGINSWITH => Conditional::CONDITION_BEGINSWITH,
+        Conditional::OPERATOR_ENDSWITH => Conditional::CONDITION_ENDSWITH,
+    ];
+
+    protected const EXPRESSIONS = [
+        Conditional::OPERATOR_CONTAINSTEXT => 'NOT(ISERROR(SEARCH(%s,%s)))',
+        Conditional::OPERATOR_NOTCONTAINS => 'ISERROR(SEARCH(%s,%s))',
+        Conditional::OPERATOR_BEGINSWITH => 'LEFT(%s,LEN(%s))=%s',
+        Conditional::OPERATOR_ENDSWITH => 'RIGHT(%s,LEN(%s))=%s',
+    ];
+
+    /** @var string */
+    protected $operator;
+
+    /** @var string */
+    protected $operand;
+
+    /**
+     * @var string
+     */
+    protected $operandValueType;
+
+    public function __construct(string $cellRange)
+    {
+        parent::__construct($cellRange);
+    }
+
+    protected function operator(string $operator): void
+    {
+        if (!isset(self::OPERATORS[$operator])) {
+            throw new Exception('Invalid Operator for Text Value CF Rule Wizard');
+        }
+
+        $this->operator = $operator;
+    }
+
+    protected function operand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): void
+    {
+        $operand = $this->validateOperand($operand, $operandValueType);
+
+        $this->operand = $operand;
+        $this->operandValueType = $operandValueType;
+    }
+
+    protected function wrapValue(string $value): string
+    {
+        return '"' . $value . '"';
+    }
+
+    protected function setExpression(): void
+    {
+        $operand = $this->operandValueType === Wizard::VALUE_TYPE_LITERAL
+            ? $this->wrapValue(str_replace('"', '""', $this->operand))
+            : $this->cellConditionCheck($this->operand);
+
+        if (
+            $this->operator === Conditional::OPERATOR_CONTAINSTEXT ||
+            $this->operator === Conditional::OPERATOR_NOTCONTAINS
+        ) {
+            $this->expression = sprintf(self::EXPRESSIONS[$this->operator], $operand, $this->referenceCell);
+        } else {
+            $this->expression = sprintf(self::EXPRESSIONS[$this->operator], $this->referenceCell, $operand, $operand);
+        }
+    }
+
+    public function getConditional(): Conditional
+    {
+        $this->setExpression();
+
+        $conditional = new Conditional();
+        $conditional->setConditionType(self::OPERATORS[$this->operator]);
+        $conditional->setOperatorType($this->operator);
+        $conditional->setText(
+            $this->operandValueType !== Wizard::VALUE_TYPE_LITERAL
+                ? $this->cellConditionCheck($this->operand)
+                : $this->operand
+        );
+        $conditional->setConditions([$this->expression]);
+        $conditional->setStyle($this->getStyle());
+        $conditional->setStopIfTrue($this->getStopIfTrue());
+
+        return $conditional;
+    }
+
+    public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
+    {
+        if (!in_array($conditional->getConditionType(), self::OPERATORS, true)) {
+            throw new Exception('Conditional is not a Text Value CF Rule conditional');
+        }
+
+        $wizard = new self($cellRange);
+        $wizard->operator = (string) array_search($conditional->getConditionType(), self::OPERATORS, true);
+        $wizard->style = $conditional->getStyle();
+        $wizard->stopIfTrue = $conditional->getStopIfTrue();
+
+        // Best-guess to try and identify if the text is a string literal, a cell reference or a formula?
+        $wizard->operandValueType = Wizard::VALUE_TYPE_LITERAL;
+        $condition = $conditional->getText();
+        if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) {
+            $wizard->operandValueType = Wizard::VALUE_TYPE_CELL;
+            $condition = self::reverseAdjustCellRef($condition, $cellRange);
+        } elseif (
+            preg_match('/\(\)/', $condition) ||
+            preg_match('/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', $condition)
+        ) {
+            $wizard->operandValueType = Wizard::VALUE_TYPE_FORMULA;
+        }
+        $wizard->operand = $condition;
+
+        return $wizard;
+    }
+
+    /**
+     * @param string $methodName
+     * @param mixed[] $arguments
+     */
+    public function __call($methodName, $arguments): self
+    {
+        if (!isset(self::MAGIC_OPERATIONS[$methodName])) {
+            throw new Exception('Invalid Operation for Text Value CF Rule Wizard');
+        }
+
+        $this->operator(self::MAGIC_OPERATIONS[$methodName]);
+        //$this->operand(...$arguments);
+        if (count($arguments) < 2) {
+            $this->operand($arguments[0]);
+        } else {
+            $this->operand($arguments[0], $arguments[1]);
+        }
+
+        return $this;
+    }
+}

+ 199 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php

@@ -0,0 +1,199 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+
+abstract class WizardAbstract
+{
+    /**
+     * @var ?Style
+     */
+    protected $style;
+
+    /**
+     * @var string
+     */
+    protected $expression;
+
+    /**
+     * @var string
+     */
+    protected $cellRange;
+
+    /**
+     * @var string
+     */
+    protected $referenceCell;
+
+    /**
+     * @var int
+     */
+    protected $referenceRow;
+
+    /**
+     * @var bool
+     */
+    protected $stopIfTrue = false;
+
+    /**
+     * @var int
+     */
+    protected $referenceColumn;
+
+    public function __construct(string $cellRange)
+    {
+        $this->setCellRange($cellRange);
+    }
+
+    public function getCellRange(): string
+    {
+        return $this->cellRange;
+    }
+
+    public function setCellRange(string $cellRange): void
+    {
+        $this->cellRange = $cellRange;
+        $this->setReferenceCellForExpressions($cellRange);
+    }
+
+    protected function setReferenceCellForExpressions(string $conditionalRange): void
+    {
+        $conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($conditionalRange)));
+        [$this->referenceCell] = $conditionalRange[0];
+
+        [$this->referenceColumn, $this->referenceRow] = Coordinate::indexesFromString($this->referenceCell);
+    }
+
+    public function getStopIfTrue(): bool
+    {
+        return $this->stopIfTrue;
+    }
+
+    public function setStopIfTrue(bool $stopIfTrue): void
+    {
+        $this->stopIfTrue = $stopIfTrue;
+    }
+
+    public function getStyle(): Style
+    {
+        return $this->style ?? new Style(false, true);
+    }
+
+    public function setStyle(Style $style): void
+    {
+        $this->style = $style;
+    }
+
+    protected function validateOperand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): string
+    {
+        if (
+            $operandValueType === Wizard::VALUE_TYPE_LITERAL &&
+            substr($operand, 0, 1) === '"' &&
+            substr($operand, -1) === '"'
+        ) {
+            $operand = str_replace('""', '"', substr($operand, 1, -1));
+        } elseif ($operandValueType === Wizard::VALUE_TYPE_FORMULA && substr($operand, 0, 1) === '=') {
+            $operand = substr($operand, 1);
+        }
+
+        return $operand;
+    }
+
+    protected static function reverseCellAdjustment(array $matches, int $referenceColumn, int $referenceRow): string
+    {
+        $worksheet = $matches[1];
+        $column = $matches[6];
+        $row = $matches[7];
+
+        if (strpos($column, '$') === false) {
+            $column = Coordinate::columnIndexFromString($column);
+            $column -= $referenceColumn - 1;
+            $column = Coordinate::stringFromColumnIndex($column);
+        }
+
+        if (strpos($row, '$') === false) {
+            $row -= $referenceRow - 1;
+        }
+
+        return "{$worksheet}{$column}{$row}";
+    }
+
+    public static function reverseAdjustCellRef(string $condition, string $cellRange): string
+    {
+        $conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($cellRange)));
+        [$referenceCell] = $conditionalRange[0];
+        [$referenceColumnIndex, $referenceRow] = Coordinate::indexesFromString($referenceCell);
+
+        $splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition);
+        $i = false;
+        foreach ($splitCondition as &$value) {
+            //    Only count/replace in alternating array entries (ie. not in quoted strings)
+            $i = $i === false;
+            if ($i) {
+                $value = (string) preg_replace_callback(
+                    '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
+                    function ($matches) use ($referenceColumnIndex, $referenceRow) {
+                        return self::reverseCellAdjustment($matches, $referenceColumnIndex, $referenceRow);
+                    },
+                    $value
+                );
+            }
+        }
+        unset($value);
+
+        //    Then rebuild the condition string to return it
+        return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition);
+    }
+
+    protected function conditionCellAdjustment(array $matches): string
+    {
+        $worksheet = $matches[1];
+        $column = $matches[6];
+        $row = $matches[7];
+
+        if (strpos($column, '$') === false) {
+            $column = Coordinate::columnIndexFromString($column);
+            $column += $this->referenceColumn - 1;
+            $column = Coordinate::stringFromColumnIndex($column);
+        }
+
+        if (strpos($row, '$') === false) {
+            $row += $this->referenceRow - 1;
+        }
+
+        return "{$worksheet}{$column}{$row}";
+    }
+
+    protected function cellConditionCheck(string $condition): string
+    {
+        $splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition);
+        $i = false;
+        foreach ($splitCondition as &$value) {
+            //    Only count/replace in alternating array entries (ie. not in quoted strings)
+            $i = $i === false;
+            if ($i) {
+                $value = (string) preg_replace_callback(
+                    '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
+                    [$this, 'conditionCellAdjustment'],
+                    $value
+                );
+            }
+        }
+        unset($value);
+
+        //    Then rebuild the condition string to return it
+        return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition);
+    }
+
+    protected function adjustConditionsForCellReferences(array $conditions): array
+    {
+        return array_map(
+            [$this, 'cellConditionCheck'],
+            $conditions
+        );
+    }
+}

+ 25 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+
+interface WizardInterface
+{
+    public function getCellRange(): string;
+
+    public function setCellRange(string $cellRange): void;
+
+    public function getStyle(): Style;
+
+    public function setStyle(Style $style): void;
+
+    public function getStopIfTrue(): bool;
+
+    public function setStopIfTrue(bool $stopIfTrue): void;
+
+    public function getConditional(): Conditional;
+
+    public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): self;
+}

+ 102 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+use NumberFormatter;
+use PhpOffice\PhpSpreadsheet\Exception;
+
+class Accounting extends Currency
+{
+    /**
+     * @param string $currencyCode the currency symbol or code to display for this mask
+     * @param int $decimals number of decimal places to display, in the range 0-30
+     * @param bool $thousandsSeparator indicator whether the thousands separator should be used, or not
+     * @param bool $currencySymbolPosition indicates whether the currency symbol comes before or after the value
+     *              Possible values are Currency::LEADING_SYMBOL and Currency::TRAILING_SYMBOL
+     * @param bool $currencySymbolSpacing indicates whether there is spacing between the currency symbol and the value
+     *              Possible values are Currency::SYMBOL_WITH_SPACING and Currency::SYMBOL_WITHOUT_SPACING
+     * @param ?string $locale Set the locale for the currency format; or leave as the default null.
+     *          If provided, Locale values must be a valid formatted locale string (e.g. 'en-GB', 'fr', uz-Arab-AF).
+     *          Note that setting a locale will override any other settings defined in this class
+     *          other than the currency code; or decimals (unless the decimals value is set to 0).
+     *
+     * @throws Exception If a provided locale code is not a valid format
+     */
+    public function __construct(
+        string $currencyCode = '$',
+        int $decimals = 2,
+        bool $thousandsSeparator = true,
+        bool $currencySymbolPosition = self::LEADING_SYMBOL,
+        bool $currencySymbolSpacing = self::SYMBOL_WITHOUT_SPACING,
+        ?string $locale = null
+    ) {
+        $this->setCurrencyCode($currencyCode);
+        $this->setThousandsSeparator($thousandsSeparator);
+        $this->setDecimals($decimals);
+        $this->setCurrencySymbolPosition($currencySymbolPosition);
+        $this->setCurrencySymbolSpacing($currencySymbolSpacing);
+        $this->setLocale($locale);
+    }
+
+    /**
+     * @throws Exception if the Intl extension and ICU version don't support Accounting formats
+     */
+    protected function getLocaleFormat(): string
+    {
+        if (version_compare(PHP_VERSION, '7.4.1', '<')) {
+            throw new Exception('The Intl extension does not support Accounting Formats below PHP 7.4.1');
+        }
+
+        if ($this->icuVersion() < 53.0) {
+            throw new Exception('The Intl extension does not support Accounting Formats without ICU 53');
+        }
+
+        // Scrutinizer does not recognize CURRENCY_ACCOUNTING
+        $formatter = new Locale($this->fullLocale, NumberFormatter::CURRENCY_ACCOUNTING);
+        $mask = $formatter->format();
+        if ($this->decimals === 0) {
+            $mask = (string) preg_replace('/\.0+/miu', '', $mask);
+        }
+
+        return str_replace('¤', $this->formatCurrencyCode(), $mask);
+    }
+
+    private function icuVersion(): float
+    {
+        [$major, $minor] = explode('.', INTL_ICU_VERSION);
+
+        return (float) "{$major}.{$minor}";
+    }
+
+    private function formatCurrencyCode(): string
+    {
+        if ($this->locale === null) {
+            return $this->currencyCode . '*';
+        }
+
+        return "[\${$this->currencyCode}-{$this->locale}]";
+    }
+
+    public function format(): string
+    {
+        if ($this->localeFormat !== null) {
+            return $this->localeFormat;
+        }
+
+        return sprintf(
+            '_-%s%s%s0%s%s%s_-',
+            $this->currencySymbolPosition === self::LEADING_SYMBOL ? $this->formatCurrencyCode() : null,
+            (
+                $this->currencySymbolPosition === self::LEADING_SYMBOL &&
+                $this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
+            ) ? "\u{a0}" : '',
+            $this->thousandsSeparator ? '#,##' : null,
+            $this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null,
+            (
+                $this->currencySymbolPosition === self::TRAILING_SYMBOL &&
+                $this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
+            ) ? "\u{a0}" : '',
+            $this->currencySymbolPosition === self::TRAILING_SYMBOL ? $this->formatCurrencyCode() : null
+        );
+    }
+}

+ 112 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Currency.php

@@ -0,0 +1,112 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+use NumberFormatter;
+use PhpOffice\PhpSpreadsheet\Exception;
+
+class Currency extends Number
+{
+    public const LEADING_SYMBOL = true;
+
+    public const TRAILING_SYMBOL = false;
+
+    public const SYMBOL_WITH_SPACING = true;
+
+    public const SYMBOL_WITHOUT_SPACING = false;
+
+    protected string $currencyCode = '$';
+
+    protected bool $currencySymbolPosition = self::LEADING_SYMBOL;
+
+    protected bool $currencySymbolSpacing = self::SYMBOL_WITHOUT_SPACING;
+
+    /**
+     * @param string $currencyCode the currency symbol or code to display for this mask
+     * @param int $decimals number of decimal places to display, in the range 0-30
+     * @param bool $thousandsSeparator indicator whether the thousands separator should be used, or not
+     * @param bool $currencySymbolPosition indicates whether the currency symbol comes before or after the value
+     *              Possible values are Currency::LEADING_SYMBOL and Currency::TRAILING_SYMBOL
+     * @param bool $currencySymbolSpacing indicates whether there is spacing between the currency symbol and the value
+     *              Possible values are Currency::SYMBOL_WITH_SPACING and Currency::SYMBOL_WITHOUT_SPACING
+     * @param ?string $locale Set the locale for the currency format; or leave as the default null.
+     *          If provided, Locale values must be a valid formatted locale string (e.g. 'en-GB', 'fr', uz-Arab-AF).
+     *          Note that setting a locale will override any other settings defined in this class
+     *          other than the currency code; or decimals (unless the decimals value is set to 0).
+     *
+     * @throws Exception If a provided locale code is not a valid format
+     */
+    public function __construct(
+        string $currencyCode = '$',
+        int $decimals = 2,
+        bool $thousandsSeparator = true,
+        bool $currencySymbolPosition = self::LEADING_SYMBOL,
+        bool $currencySymbolSpacing = self::SYMBOL_WITHOUT_SPACING,
+        ?string $locale = null
+    ) {
+        $this->setCurrencyCode($currencyCode);
+        $this->setThousandsSeparator($thousandsSeparator);
+        $this->setDecimals($decimals);
+        $this->setCurrencySymbolPosition($currencySymbolPosition);
+        $this->setCurrencySymbolSpacing($currencySymbolSpacing);
+        $this->setLocale($locale);
+    }
+
+    public function setCurrencyCode(string $currencyCode): void
+    {
+        $this->currencyCode = $currencyCode;
+    }
+
+    public function setCurrencySymbolPosition(bool $currencySymbolPosition = self::LEADING_SYMBOL): void
+    {
+        $this->currencySymbolPosition = $currencySymbolPosition;
+    }
+
+    public function setCurrencySymbolSpacing(bool $currencySymbolSpacing = self::SYMBOL_WITHOUT_SPACING): void
+    {
+        $this->currencySymbolSpacing = $currencySymbolSpacing;
+    }
+
+    protected function getLocaleFormat(): string
+    {
+        $formatter = new Locale($this->fullLocale, NumberFormatter::CURRENCY);
+        $mask = $formatter->format();
+        if ($this->decimals === 0) {
+            $mask = (string) preg_replace('/\.0+/miu', '', $mask);
+        }
+
+        return str_replace('¤', $this->formatCurrencyCode(), $mask);
+    }
+
+    private function formatCurrencyCode(): string
+    {
+        if ($this->locale === null) {
+            return $this->currencyCode;
+        }
+
+        return "[\${$this->currencyCode}-{$this->locale}]";
+    }
+
+    public function format(): string
+    {
+        if ($this->localeFormat !== null) {
+            return $this->localeFormat;
+        }
+
+        return sprintf(
+            '%s%s%s0%s%s%s',
+            $this->currencySymbolPosition === self::LEADING_SYMBOL ? $this->formatCurrencyCode() : null,
+            (
+                $this->currencySymbolPosition === self::LEADING_SYMBOL &&
+                $this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
+            ) ? "\u{a0}" : '',
+            $this->thousandsSeparator ? '#,##' : null,
+            $this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null,
+            (
+                $this->currencySymbolPosition === self::TRAILING_SYMBOL &&
+                $this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
+            ) ? "\u{a0}" : '',
+            $this->currencySymbolPosition === self::TRAILING_SYMBOL ? $this->formatCurrencyCode() : null
+        );
+    }
+}

+ 125 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+class Date extends DateTimeWizard
+{
+    /**
+     * Year (4 digits), e.g. 2023.
+     */
+    public const YEAR_FULL = 'yyyy';
+
+    /**
+     * Year (last 2 digits), e.g. 23.
+     */
+    public const YEAR_SHORT = 'yy';
+
+    public const MONTH_FIRST_LETTER = 'mmmmm';
+    /**
+     * Month name, long form, e.g. January.
+     */
+    public const MONTH_NAME_FULL = 'mmmm';
+    /**
+     * Month name, short form, e.g. Jan.
+     */
+    public const MONTH_NAME_SHORT = 'mmm';
+    /**
+     * Month number with a leading zero if required, e.g. 01.
+     */
+    public const MONTH_NUMBER_LONG = 'mm';
+
+    /**
+     * Month number without a leading zero, e.g. 1.
+     */
+    public const MONTH_NUMBER_SHORT = 'm';
+
+    /**
+     * Day of the week, full form, e.g. Tuesday.
+     */
+    public const WEEKDAY_NAME_LONG = 'dddd';
+
+    /**
+     * Day of the week, short form, e.g. Tue.
+     */
+    public const WEEKDAY_NAME_SHORT = 'ddd';
+
+    /**
+     * Day number with a leading zero, e.g. 03.
+     */
+    public const DAY_NUMBER_LONG = 'dd';
+
+    /**
+     * Day number without a leading zero, e.g. 3.
+     */
+    public const DAY_NUMBER_SHORT = 'd';
+
+    protected const DATE_BLOCKS = [
+        self::YEAR_FULL,
+        self::YEAR_SHORT,
+        self::MONTH_FIRST_LETTER,
+        self::MONTH_NAME_FULL,
+        self::MONTH_NAME_SHORT,
+        self::MONTH_NUMBER_LONG,
+        self::MONTH_NUMBER_SHORT,
+        self::WEEKDAY_NAME_LONG,
+        self::WEEKDAY_NAME_SHORT,
+        self::DAY_NUMBER_LONG,
+        self::DAY_NUMBER_SHORT,
+    ];
+
+    public const SEPARATOR_DASH = '-';
+    public const SEPARATOR_DOT = '.';
+    public const SEPARATOR_SLASH = '/';
+    public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}";
+    public const SEPARATOR_SPACE = ' ';
+
+    protected const DATE_DEFAULT = [
+        self::YEAR_FULL,
+        self::MONTH_NUMBER_LONG,
+        self::DAY_NUMBER_LONG,
+    ];
+
+    /**
+     * @var string[]
+     */
+    protected array $separators;
+
+    /**
+     * @var string[]
+     */
+    protected array $formatBlocks;
+
+    /**
+     * @param null|string|string[] $separators
+     *        If you want to use the same separator for all format blocks, then it can be passed as a string literal;
+     *           if you wish to use different separators, then they should be passed as an array.
+     *        If you want to use only a single format block, then pass a null as the separator argument
+     */
+    public function __construct($separators = self::SEPARATOR_DASH, string ...$formatBlocks)
+    {
+        $separators ??= self::SEPARATOR_DASH;
+        $formatBlocks = (count($formatBlocks) === 0) ? self::DATE_DEFAULT : $formatBlocks;
+
+        $this->separators = $this->padSeparatorArray(
+            is_array($separators) ? $separators : [$separators],
+            count($formatBlocks) - 1
+        );
+        $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
+    }
+
+    private function mapFormatBlocks(string $value): string
+    {
+        // Any date masking codes are returned as lower case values
+        if (in_array(mb_strtolower($value), self::DATE_BLOCKS, true)) {
+            return mb_strtolower($value);
+        }
+
+        // Wrap any string literals in quotes, so that they're clearly defined as string literals
+        return $this->wrapLiteral($value);
+    }
+
+    public function format(): string
+    {
+        return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
+    }
+}

+ 50 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+class DateTime extends DateTimeWizard
+{
+    /**
+     * @var string[]
+     */
+    protected array $separators;
+
+    /**
+     * @var array<DateTimeWizard|string>
+     */
+    protected array $formatBlocks;
+
+    /**
+     * @param null|string|string[] $separators
+     *          If you want to use only a single format block, then pass a null as the separator argument
+     * @param DateTimeWizard|string ...$formatBlocks
+     */
+    public function __construct($separators, ...$formatBlocks)
+    {
+        $this->separators = $this->padSeparatorArray(
+            is_array($separators) ? $separators : [$separators],
+            count($formatBlocks) - 1
+        );
+        $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
+    }
+
+    /**
+     * @param DateTimeWizard|string $value
+     */
+    private function mapFormatBlocks($value): string
+    {
+        // Any date masking codes are returned as lower case values
+        if (is_object($value)) {
+            // We can't explicitly test for Stringable until PHP >= 8.0
+            return $value;
+        }
+
+        // Wrap any string literals in quotes, so that they're clearly defined as string literals
+        return $this->wrapLiteral($value);
+    }
+
+    public function format(): string
+    {
+        return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
+    }
+}

+ 44 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+abstract class DateTimeWizard implements Wizard
+{
+    protected const NO_ESCAPING_NEEDED = "$+-/():!^&'~{}<>= ";
+
+    protected function padSeparatorArray(array $separators, int $count): array
+    {
+        $lastSeparator = array_pop($separators);
+
+        return $separators + array_fill(0, $count, $lastSeparator);
+    }
+
+    protected function escapeSingleCharacter(string $value): string
+    {
+        if (strpos(self::NO_ESCAPING_NEEDED, $value) !== false) {
+            return $value;
+        }
+
+        return "\\{$value}";
+    }
+
+    protected function wrapLiteral(string $value): string
+    {
+        if (mb_strlen($value, 'UTF-8') === 1) {
+            return $this->escapeSingleCharacter($value);
+        }
+
+        // Wrap any other string literals in quotes, so that they're clearly defined as string literals
+        return '"' . str_replace('"', '""', $value) . '"';
+    }
+
+    protected function intersperse(string $formatBlock, ?string $separator): string
+    {
+        return "{$formatBlock}{$separator}";
+    }
+
+    public function __toString(): string
+    {
+        return $this->format();
+    }
+}

+ 153 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php

@@ -0,0 +1,153 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+class Duration extends DateTimeWizard
+{
+    public const DAYS_DURATION = 'd';
+
+    /**
+     * Hours as a duration (can exceed 24), e.g. 29.
+     */
+    public const HOURS_DURATION = '[h]';
+
+    /**
+     * Hours without a leading zero, e.g. 9.
+     */
+    public const HOURS_SHORT = 'h';
+
+    /**
+     * Hours with a leading zero, e.g. 09.
+     */
+    public const HOURS_LONG = 'hh';
+
+    /**
+     * Minutes as a duration (can exceed 60), e.g. 109.
+     */
+    public const MINUTES_DURATION = '[m]';
+
+    /**
+     * Minutes without a leading zero, e.g. 5.
+     */
+    public const MINUTES_SHORT = 'm';
+
+    /**
+     * Minutes with a leading zero, e.g. 05.
+     */
+    public const MINUTES_LONG = 'mm';
+
+    /**
+     * Seconds as a duration (can exceed 60), e.g. 129.
+     */
+    public const SECONDS_DURATION = '[s]';
+
+    /**
+     * Seconds without a leading zero, e.g. 2.
+     */
+    public const SECONDS_SHORT = 's';
+
+    /**
+     * Seconds with a leading zero, e.g. 02.
+     */
+    public const SECONDS_LONG = 'ss';
+
+    protected const DURATION_BLOCKS = [
+        self::DAYS_DURATION,
+        self::HOURS_DURATION,
+        self::HOURS_LONG,
+        self::HOURS_SHORT,
+        self::MINUTES_DURATION,
+        self::MINUTES_LONG,
+        self::MINUTES_SHORT,
+        self::SECONDS_DURATION,
+        self::SECONDS_LONG,
+        self::SECONDS_SHORT,
+    ];
+
+    protected const DURATION_MASKS = [
+        self::DAYS_DURATION => self::DAYS_DURATION,
+        self::HOURS_DURATION => self::HOURS_SHORT,
+        self::MINUTES_DURATION => self::MINUTES_LONG,
+        self::SECONDS_DURATION => self::SECONDS_LONG,
+    ];
+
+    protected const DURATION_DEFAULTS = [
+        self::HOURS_LONG => self::HOURS_DURATION,
+        self::HOURS_SHORT => self::HOURS_DURATION,
+        self::MINUTES_LONG => self::MINUTES_DURATION,
+        self::MINUTES_SHORT => self::MINUTES_DURATION,
+        self::SECONDS_LONG => self::SECONDS_DURATION,
+        self::SECONDS_SHORT => self::SECONDS_DURATION,
+    ];
+
+    public const SEPARATOR_COLON = ':';
+    public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}";
+    public const SEPARATOR_SPACE = ' ';
+
+    public const DURATION_DEFAULT = [
+        self::HOURS_DURATION,
+        self::MINUTES_LONG,
+        self::SECONDS_LONG,
+    ];
+
+    /**
+     * @var string[]
+     */
+    protected array $separators;
+
+    /**
+     * @var string[]
+     */
+    protected array $formatBlocks;
+
+    protected bool $durationIsSet = false;
+
+    /**
+     * @param null|string|string[] $separators
+     *        If you want to use the same separator for all format blocks, then it can be passed as a string literal;
+     *           if you wish to use different separators, then they should be passed as an array.
+     *        If you want to use only a single format block, then pass a null as the separator argument
+     */
+    public function __construct($separators = self::SEPARATOR_COLON, string ...$formatBlocks)
+    {
+        $separators ??= self::SEPARATOR_COLON;
+        $formatBlocks = (count($formatBlocks) === 0) ? self::DURATION_DEFAULT : $formatBlocks;
+
+        $this->separators = $this->padSeparatorArray(
+            is_array($separators) ? $separators : [$separators],
+            count($formatBlocks) - 1
+        );
+        $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
+
+        if ($this->durationIsSet === false) {
+            // We need at least one duration mask, so if none has been set we change the first mask element
+            //    to a duration.
+            $this->formatBlocks[0] = self::DURATION_DEFAULTS[mb_strtolower($this->formatBlocks[0])];
+        }
+    }
+
+    private function mapFormatBlocks(string $value): string
+    {
+        // Any duration masking codes are returned as lower case values
+        if (in_array(mb_strtolower($value), self::DURATION_BLOCKS, true)) {
+            if (array_key_exists(mb_strtolower($value), self::DURATION_MASKS)) {
+                if ($this->durationIsSet) {
+                    // We should only have a single duration mask, the first defined in the mask set,
+                    //    so convert any additional duration masks to standard time masks.
+                    $value = self::DURATION_MASKS[mb_strtolower($value)];
+                }
+                $this->durationIsSet = true;
+            }
+
+            return mb_strtolower($value);
+        }
+
+        // Wrap any string literals in quotes, so that they're clearly defined as string literals
+        return $this->wrapLiteral($value);
+    }
+
+    public function format(): string
+    {
+        return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
+    }
+}

+ 37 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+use NumberFormatter;
+use PhpOffice\PhpSpreadsheet\Exception;
+
+final class Locale
+{
+    /**
+     * Language code: ISO-639 2 character, alpha.
+     * Optional script code: ISO-15924 4 alpha.
+     * Optional country code: ISO-3166-1, 2 character alpha.
+     * Separated by underscores or dashes.
+     */
+    public const STRUCTURE = '/^(?P<language>[a-z]{2})([-_](?P<script>[a-z]{4}))?([-_](?P<country>[a-z]{2}))?$/i';
+
+    private NumberFormatter $formatter;
+
+    public function __construct(?string $locale, int $style)
+    {
+        if (class_exists(NumberFormatter::class) === false) {
+            throw new Exception();
+        }
+
+        $formatterLocale = str_replace('-', '_', $locale ?? '');
+        $this->formatter = new NumberFormatter($formatterLocale, $style);
+        if ($this->formatter->getLocale() !== $formatterLocale) {
+            throw new Exception("Unable to read locale data for '{$locale}'");
+        }
+    }
+
+    public function format(): string
+    {
+        return $this->formatter->getPattern();
+    }
+}

+ 57 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Number.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+
+class Number extends NumberBase implements Wizard
+{
+    public const WITH_THOUSANDS_SEPARATOR = true;
+
+    public const WITHOUT_THOUSANDS_SEPARATOR = false;
+
+    protected bool $thousandsSeparator = true;
+
+    /**
+     * @param int $decimals number of decimal places to display, in the range 0-30
+     * @param bool $thousandsSeparator indicator whether the thousands separator should be used, or not
+     * @param ?string $locale Set the locale for the number format; or leave as the default null.
+     *          Locale has no effect for Number Format values, and is retained here only for compatibility
+     *              with the other Wizards.
+     *          If provided, Locale values must be a valid formatted locale string (e.g. 'en-GB', 'fr', uz-Arab-AF).
+     *
+     * @throws Exception If a provided locale code is not a valid format
+     */
+    public function __construct(
+        int $decimals = 2,
+        bool $thousandsSeparator = self::WITH_THOUSANDS_SEPARATOR,
+        ?string $locale = null
+    ) {
+        $this->setDecimals($decimals);
+        $this->setThousandsSeparator($thousandsSeparator);
+        $this->setLocale($locale);
+    }
+
+    public function setThousandsSeparator(bool $thousandsSeparator = self::WITH_THOUSANDS_SEPARATOR): void
+    {
+        $this->thousandsSeparator = $thousandsSeparator;
+    }
+
+    /**
+     * As MS Excel cannot easily handle Lakh, which is the only locale-specific Number format variant,
+     *       we don't use locale with Numbers.
+     */
+    protected function getLocaleFormat(): string
+    {
+        return $this->format();
+    }
+
+    public function format(): string
+    {
+        return sprintf(
+            '%s0%s',
+            $this->thousandsSeparator ? '#,##' : null,
+            $this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null
+        );
+    }
+}

+ 80 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+use NumberFormatter;
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+
+abstract class NumberBase
+{
+    protected const MAX_DECIMALS = 30;
+
+    protected int $decimals = 2;
+
+    protected ?string $locale = null;
+
+    protected ?string $fullLocale = null;
+
+    protected ?string $localeFormat = null;
+
+    public function setDecimals(int $decimals = 2): void
+    {
+        $this->decimals = ($decimals > self::MAX_DECIMALS) ? self::MAX_DECIMALS : max($decimals, 0);
+    }
+
+    /**
+     * Setting a locale will override any settings defined in this class.
+     *
+     * @throws Exception If the locale code is not a valid format
+     */
+    public function setLocale(?string $locale = null): void
+    {
+        if ($locale === null) {
+            $this->localeFormat = $this->locale = $this->fullLocale = null;
+
+            return;
+        }
+
+        $this->locale = $this->validateLocale($locale);
+
+        if (class_exists(NumberFormatter::class)) {
+            $this->localeFormat = $this->getLocaleFormat();
+        }
+    }
+
+    /**
+     * Stub: should be implemented as a concrete method in concrete wizards.
+     */
+    abstract protected function getLocaleFormat(): string;
+
+    /**
+     * @throws Exception If the locale code is not a valid format
+     */
+    private function validateLocale(string $locale): string
+    {
+        if (preg_match(Locale::STRUCTURE, $locale, $matches, PREG_UNMATCHED_AS_NULL) !== 1) {
+            throw new Exception("Invalid locale code '{$locale}'");
+        }
+
+        ['language' => $language, 'script' => $script, 'country' => $country] = $matches;
+        // Set case and separator to match standardised locale case
+        $language = strtolower($language ?? '');
+        $script = ($script === null) ? null : ucfirst(strtolower($script));
+        $country = ($country === null) ? null : strtoupper($country);
+
+        $this->fullLocale = implode('-', array_filter([$language, $script, $country]));
+
+        return $country === null ? $language : "{$language}-{$country}";
+    }
+
+    public function format(): string
+    {
+        return NumberFormat::FORMAT_GENERAL;
+    }
+
+    public function __toString(): string
+    {
+        return $this->format();
+    }
+}

+ 40 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Percentage.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+use NumberFormatter;
+use PhpOffice\PhpSpreadsheet\Exception;
+
+class Percentage extends NumberBase implements Wizard
+{
+    /**
+     * @param int $decimals number of decimal places to display, in the range 0-30
+     * @param ?string $locale Set the locale for the percentage format; or leave as the default null.
+     *          If provided, Locale values must be a valid formatted locale string (e.g. 'en-GB', 'fr', uz-Arab-AF).
+     *
+     * @throws Exception If a provided locale code is not a valid format
+     */
+    public function __construct(int $decimals = 2, ?string $locale = null)
+    {
+        $this->setDecimals($decimals);
+        $this->setLocale($locale);
+    }
+
+    protected function getLocaleFormat(): string
+    {
+        $formatter = new Locale($this->fullLocale, NumberFormatter::PERCENT);
+
+        return $this->decimals > 0
+            ? str_replace('0', '0.' . str_repeat('0', $this->decimals), $formatter->format())
+            : $formatter->format();
+    }
+
+    public function format(): string
+    {
+        if ($this->localeFormat !== null) {
+            return $this->localeFormat;
+        }
+
+        return sprintf('0%s%%', $this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null);
+    }
+}

+ 33 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Scientific.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+
+class Scientific extends NumberBase implements Wizard
+{
+    /**
+     * @param int $decimals number of decimal places to display, in the range 0-30
+     * @param ?string $locale Set the locale for the scientific format; or leave as the default null.
+     *          Locale has no effect for Scientific Format values, and is retained here for compatibility
+     *              with the other Wizards.
+     *          If provided, Locale values must be a valid formatted locale string (e.g. 'en-GB', 'fr', uz-Arab-AF).
+     *
+     * @throws Exception If a provided locale code is not a valid format
+     */
+    public function __construct(int $decimals = 2, ?string $locale = null)
+    {
+        $this->setDecimals($decimals);
+        $this->setLocale($locale);
+    }
+
+    protected function getLocaleFormat(): string
+    {
+        return $this->format();
+    }
+
+    public function format(): string
+    {
+        return sprintf('0%sE+00', $this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null);
+    }
+}

+ 105 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php

@@ -0,0 +1,105 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+class Time extends DateTimeWizard
+{
+    /**
+     * Hours without a leading zero, e.g. 9.
+     */
+    public const HOURS_SHORT = 'h';
+
+    /**
+     * Hours with a leading zero, e.g. 09.
+     */
+    public const HOURS_LONG = 'hh';
+
+    /**
+     * Minutes without a leading zero, e.g. 5.
+     */
+    public const MINUTES_SHORT = 'm';
+
+    /**
+     * Minutes with a leading zero, e.g. 05.
+     */
+    public const MINUTES_LONG = 'mm';
+
+    /**
+     * Seconds without a leading zero, e.g. 2.
+     */
+    public const SECONDS_SHORT = 's';
+
+    /**
+     * Seconds with a leading zero, e.g. 02.
+     */
+    public const SECONDS_LONG = 'ss';
+
+    public const MORNING_AFTERNOON = 'AM/PM';
+
+    protected const TIME_BLOCKS = [
+        self::HOURS_LONG,
+        self::HOURS_SHORT,
+        self::MINUTES_LONG,
+        self::MINUTES_SHORT,
+        self::SECONDS_LONG,
+        self::SECONDS_SHORT,
+        self::MORNING_AFTERNOON,
+    ];
+
+    public const SEPARATOR_COLON = ':';
+    public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}";
+    public const SEPARATOR_SPACE = ' ';
+
+    protected const TIME_DEFAULT = [
+        self::HOURS_LONG,
+        self::MINUTES_LONG,
+        self::SECONDS_LONG,
+    ];
+
+    /**
+     * @var string[]
+     */
+    protected array $separators;
+
+    /**
+     * @var string[]
+     */
+    protected array $formatBlocks;
+
+    /**
+     * @param null|string|string[] $separators
+     *        If you want to use the same separator for all format blocks, then it can be passed as a string literal;
+     *           if you wish to use different separators, then they should be passed as an array.
+     *        If you want to use only a single format block, then pass a null as the separator argument
+     */
+    public function __construct($separators = self::SEPARATOR_COLON, string ...$formatBlocks)
+    {
+        $separators ??= self::SEPARATOR_COLON;
+        $formatBlocks = (count($formatBlocks) === 0) ? self::TIME_DEFAULT : $formatBlocks;
+
+        $this->separators = $this->padSeparatorArray(
+            is_array($separators) ? $separators : [$separators],
+            count($formatBlocks) - 1
+        );
+        $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
+    }
+
+    private function mapFormatBlocks(string $value): string
+    {
+        // Any date masking codes are returned as lower case values
+        //     except for AM/PM, which is set to uppercase
+        if (in_array(mb_strtolower($value), self::TIME_BLOCKS, true)) {
+            return mb_strtolower($value);
+        } elseif (mb_strtoupper($value) === self::MORNING_AFTERNOON) {
+            return mb_strtoupper($value);
+        }
+
+        // Wrap any string literals in quotes, so that they're clearly defined as string literals
+        return $this->wrapLiteral($value);
+    }
+
+    public function format(): string
+    {
+        return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
+    }
+}

+ 8 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Wizard.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+
+interface Wizard
+{
+    public function format(): string;
+}

+ 175 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php

@@ -0,0 +1,175 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+/**
+ * Class to handle tint applied to color.
+ * Code borrows heavily from some Python projects.
+ *
+ * @see https://docs.python.org/3/library/colorsys.html
+ * @see https://gist.github.com/Mike-Honey/b36e651e9a7f1d2e1d60ce1c63b9b633
+ */
+class RgbTint
+{
+    private const ONE_THIRD = 1.0 / 3.0;
+    private const ONE_SIXTH = 1.0 / 6.0;
+    private const TWO_THIRD = 2.0 / 3.0;
+    private const RGBMAX = 255.0;
+    /**
+     * MS excel's tint function expects that HLS is base 240.
+     *
+     * @see https://social.msdn.microsoft.com/Forums/en-US/e9d8c136-6d62-4098-9b1b-dac786149f43/excel-color-tint-algorithm-incorrect?forum=os_binaryfile#d3c2ac95-52e0-476b-86f1-e2a697f24969
+     */
+    private const HLSMAX = 240.0;
+
+    /**
+     * Convert red/green/blue to hue/luminance/saturation.
+     *
+     * @param float $red 0.0 through 1.0
+     * @param float $green 0.0 through 1.0
+     * @param float $blue 0.0 through 1.0
+     *
+     * @return float[]
+     */
+    private static function rgbToHls(float $red, float $green, float $blue): array
+    {
+        $maxc = max($red, $green, $blue);
+        $minc = min($red, $green, $blue);
+        $luminance = ($minc + $maxc) / 2.0;
+        if ($minc === $maxc) {
+            return [0.0, $luminance, 0.0];
+        }
+        $maxMinusMin = $maxc - $minc;
+        if ($luminance <= 0.5) {
+            $s = $maxMinusMin / ($maxc + $minc);
+        } else {
+            $s = $maxMinusMin / (2.0 - $maxc - $minc);
+        }
+        $rc = ($maxc - $red) / $maxMinusMin;
+        $gc = ($maxc - $green) / $maxMinusMin;
+        $bc = ($maxc - $blue) / $maxMinusMin;
+        if ($red === $maxc) {
+            $h = $bc - $gc;
+        } elseif ($green === $maxc) {
+            $h = 2.0 + $rc - $bc;
+        } else {
+            $h = 4.0 + $gc - $rc;
+        }
+        $h = self::positiveDecimalPart($h / 6.0);
+
+        return [$h, $luminance, $s];
+    }
+
+    /** @var mixed */
+    private static $scrutinizerZeroPointZero = 0.0;
+
+    /**
+     * Convert hue/luminance/saturation to red/green/blue.
+     *
+     * @param float $hue 0.0 through 1.0
+     * @param float $luminance 0.0 through 1.0
+     * @param float $saturation 0.0 through 1.0
+     *
+     * @return float[]
+     */
+    private static function hlsToRgb($hue, $luminance, $saturation): array
+    {
+        if ($saturation === self::$scrutinizerZeroPointZero) {
+            return [$luminance, $luminance, $luminance];
+        }
+        if ($luminance <= 0.5) {
+            $m2 = $luminance * (1.0 + $saturation);
+        } else {
+            $m2 = $luminance + $saturation - ($luminance * $saturation);
+        }
+        $m1 = 2.0 * $luminance - $m2;
+
+        return [
+            self::vFunction($m1, $m2, $hue + self::ONE_THIRD),
+            self::vFunction($m1, $m2, $hue),
+            self::vFunction($m1, $m2, $hue - self::ONE_THIRD),
+        ];
+    }
+
+    private static function vFunction(float $m1, float $m2, float $hue): float
+    {
+        $hue = self::positiveDecimalPart($hue);
+        if ($hue < self::ONE_SIXTH) {
+            return $m1 + ($m2 - $m1) * $hue * 6.0;
+        }
+        if ($hue < 0.5) {
+            return $m2;
+        }
+        if ($hue < self::TWO_THIRD) {
+            return $m1 + ($m2 - $m1) * (self::TWO_THIRD - $hue) * 6.0;
+        }
+
+        return $m1;
+    }
+
+    private static function positiveDecimalPart(float $hue): float
+    {
+        $hue = fmod($hue, 1.0);
+
+        return ($hue >= 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)
+        );
+    }
+}

+ 269 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Theme.php

@@ -0,0 +1,269 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+class Theme
+{
+    /** @var string */
+    private $themeColorName = 'Office';
+
+    /** @var string */
+    private $themeFontName = 'Office';
+
+    public const COLOR_SCHEME_2013_PLUS_NAME = 'Office 2013+';
+    public const COLOR_SCHEME_2013_PLUS = [
+        'dk1' => '000000',
+        'lt1' => 'FFFFFF',
+        'dk2' => '44546A',
+        'lt2' => 'E7E6E6',
+        'accent1' => '4472C4',
+        'accent2' => 'ED7D31',
+        'accent3' => 'A5A5A5',
+        'accent4' => 'FFC000',
+        'accent5' => '5B9BD5',
+        'accent6' => '70AD47',
+        'hlink' => '0563C1',
+        'folHlink' => '954F72',
+    ];
+
+    public const COLOR_SCHEME_2007_2010_NAME = 'Office 2007-2010';
+    public const COLOR_SCHEME_2007_2010 = [
+        'dk1' => '000000',
+        'lt1' => 'FFFFFF',
+        'dk2' => '1F497D',
+        'lt2' => 'EEECE1',
+        'accent1' => '4F81BD',
+        'accent2' => 'C0504D',
+        'accent3' => '9BBB59',
+        'accent4' => '8064A2',
+        'accent5' => '4BACC6',
+        'accent6' => 'F79646',
+        'hlink' => '0000FF',
+        'folHlink' => '800080',
+    ];
+
+    /** @var string[] */
+    private $themeColors = self::COLOR_SCHEME_2007_2010;
+
+    /** @var string */
+    private $majorFontLatin = 'Cambria';
+
+    /** @var string */
+    private $majorFontEastAsian = '';
+
+    /** @var string */
+    private $majorFontComplexScript = '';
+
+    /** @var string */
+    private $minorFontLatin = 'Calibri';
+
+    /** @var string */
+    private $minorFontEastAsian = '';
+
+    /** @var string */
+    private $minorFontComplexScript = '';
+
+    /**
+     * Map of Major (header) fonts to write.
+     *
+     * @var string[]
+     */
+    private $majorFontSubstitutions = self::FONTS_TIMES_SUBSTITUTIONS;
+
+    /**
+     * Map of Minor (body) fonts to write.
+     *
+     * @var string[]
+     */
+    private $minorFontSubstitutions = self::FONTS_ARIAL_SUBSTITUTIONS;
+
+    public const FONTS_TIMES_SUBSTITUTIONS = [
+        'Jpan' => 'MS Pゴシック',
+        'Hang' => '맑은 고딕',
+        'Hans' => '宋体',
+        'Hant' => '新細明體',
+        'Arab' => 'Times New Roman',
+        'Hebr' => 'Times New Roman',
+        'Thai' => 'Tahoma',
+        'Ethi' => 'Nyala',
+        'Beng' => 'Vrinda',
+        'Gujr' => 'Shruti',
+        'Khmr' => 'MoolBoran',
+        'Knda' => 'Tunga',
+        'Guru' => 'Raavi',
+        'Cans' => 'Euphemia',
+        'Cher' => 'Plantagenet Cherokee',
+        'Yiii' => 'Microsoft Yi Baiti',
+        'Tibt' => 'Microsoft Himalaya',
+        'Thaa' => 'MV Boli',
+        'Deva' => 'Mangal',
+        'Telu' => 'Gautami',
+        'Taml' => 'Latha',
+        'Syrc' => 'Estrangelo Edessa',
+        'Orya' => 'Kalinga',
+        'Mlym' => 'Kartika',
+        'Laoo' => 'DokChampa',
+        'Sinh' => 'Iskoola Pota',
+        'Mong' => 'Mongolian Baiti',
+        'Viet' => 'Times New Roman',
+        'Uigh' => 'Microsoft Uighur',
+        'Geor' => 'Sylfaen',
+    ];
+
+    public const FONTS_ARIAL_SUBSTITUTIONS = [
+        'Jpan' => 'MS Pゴシック',
+        'Hang' => '맑은 고딕',
+        'Hans' => '宋体',
+        'Hant' => '新細明體',
+        'Arab' => 'Arial',
+        'Hebr' => 'Arial',
+        'Thai' => 'Tahoma',
+        'Ethi' => 'Nyala',
+        'Beng' => 'Vrinda',
+        'Gujr' => 'Shruti',
+        'Khmr' => 'DaunPenh',
+        'Knda' => 'Tunga',
+        'Guru' => 'Raavi',
+        'Cans' => 'Euphemia',
+        'Cher' => 'Plantagenet Cherokee',
+        'Yiii' => 'Microsoft Yi Baiti',
+        'Tibt' => 'Microsoft Himalaya',
+        'Thaa' => 'MV Boli',
+        'Deva' => 'Mangal',
+        'Telu' => 'Gautami',
+        'Taml' => 'Latha',
+        'Syrc' => 'Estrangelo Edessa',
+        'Orya' => 'Kalinga',
+        'Mlym' => 'Kartika',
+        'Laoo' => 'DokChampa',
+        'Sinh' => 'Iskoola Pota',
+        'Mong' => 'Mongolian Baiti',
+        'Viet' => 'Arial',
+        'Uigh' => 'Microsoft Uighur',
+        'Geor' => 'Sylfaen',
+    ];
+
+    public function getThemeColors(): array
+    {
+        return $this->themeColors;
+    }
+
+    public function setThemeColor(string $key, string $value): self
+    {
+        $this->themeColors[$key] = $value;
+
+        return $this;
+    }
+
+    public function getThemeColorName(): string
+    {
+        return $this->themeColorName;
+    }
+
+    public function setThemeColorName(string $name, ?array $themeColors = null): self
+    {
+        $this->themeColorName = $name;
+        if ($name === self::COLOR_SCHEME_2007_2010_NAME) {
+            $themeColors = $themeColors ?? self::COLOR_SCHEME_2007_2010;
+        } elseif ($name === self::COLOR_SCHEME_2013_PLUS_NAME) {
+            $themeColors = $themeColors ?? self::COLOR_SCHEME_2013_PLUS;
+        }
+        if ($themeColors !== null) {
+            $this->themeColors = $themeColors;
+        }
+
+        return $this;
+    }
+
+    public function getMajorFontLatin(): string
+    {
+        return $this->majorFontLatin;
+    }
+
+    public function getMajorFontEastAsian(): string
+    {
+        return $this->majorFontEastAsian;
+    }
+
+    public function getMajorFontComplexScript(): string
+    {
+        return $this->majorFontComplexScript;
+    }
+
+    public function getMajorFontSubstitutions(): array
+    {
+        return $this->majorFontSubstitutions;
+    }
+
+    /** @param null|array $substitutions */
+    public function setMajorFontValues(?string $latin, ?string $eastAsian, ?string $complexScript, $substitutions): self
+    {
+        if (!empty($latin)) {
+            $this->majorFontLatin = $latin;
+        }
+        if ($eastAsian !== null) {
+            $this->majorFontEastAsian = $eastAsian;
+        }
+        if ($complexScript !== null) {
+            $this->majorFontComplexScript = $complexScript;
+        }
+        if ($substitutions !== null) {
+            $this->majorFontSubstitutions = $substitutions;
+        }
+
+        return $this;
+    }
+
+    public function getMinorFontLatin(): string
+    {
+        return $this->minorFontLatin;
+    }
+
+    public function getMinorFontEastAsian(): string
+    {
+        return $this->minorFontEastAsian;
+    }
+
+    public function getMinorFontComplexScript(): string
+    {
+        return $this->minorFontComplexScript;
+    }
+
+    public function getMinorFontSubstitutions(): array
+    {
+        return $this->minorFontSubstitutions;
+    }
+
+    /** @param null|array $substitutions */
+    public function setMinorFontValues(?string $latin, ?string $eastAsian, ?string $complexScript, $substitutions): self
+    {
+        if (!empty($latin)) {
+            $this->minorFontLatin = $latin;
+        }
+        if ($eastAsian !== null) {
+            $this->minorFontEastAsian = $eastAsian;
+        }
+        if ($complexScript !== null) {
+            $this->minorFontComplexScript = $complexScript;
+        }
+        if ($substitutions !== null) {
+            $this->minorFontSubstitutions = $substitutions;
+        }
+
+        return $this;
+    }
+
+    public function getThemeFontName(): string
+    {
+        return $this->themeFontName;
+    }
+
+    public function setThemeFontName(?string $name): self
+    {
+        if (!empty($name)) {
+            $this->themeFontName = $name;
+        }
+
+        return $this;
+    }
+}

+ 51 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFit.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
+use PhpOffice\PhpSpreadsheet\Cell\CellRange;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+
+class AutoFit
+{
+    protected Worksheet $worksheet;
+
+    public function __construct(Worksheet $worksheet)
+    {
+        $this->worksheet = $worksheet;
+    }
+
+    public function getAutoFilterIndentRanges(): array
+    {
+        $autoFilterIndentRanges = [];
+        $autoFilterIndentRanges[] = $this->getAutoFilterIndentRange($this->worksheet->getAutoFilter());
+
+        foreach ($this->worksheet->getTableCollection() as $table) {
+            /** @var Table $table */
+            if ($table->getShowHeaderRow() === true && $table->getAllowFilter() === true) {
+                $autoFilter = $table->getAutoFilter();
+                if ($autoFilter !== null) {
+                    $autoFilterIndentRanges[] = $this->getAutoFilterIndentRange($autoFilter);
+                }
+            }
+        }
+
+        return array_filter($autoFilterIndentRanges);
+    }
+
+    private function getAutoFilterIndentRange(AutoFilter $autoFilter): ?string
+    {
+        $autoFilterRange = $autoFilter->getRange();
+        $autoFilterIndentRange = null;
+
+        if (!empty($autoFilterRange)) {
+            $autoFilterRangeBoundaries = Coordinate::rangeBoundaries($autoFilterRange);
+            $autoFilterIndentRange = (string) new CellRange(
+                CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[0][0], $autoFilterRangeBoundaries[0][1]),
+                CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[1][0], $autoFilterRangeBoundaries[0][1])
+            );
+        }
+
+        return $autoFilterIndentRange;
+    }
+}

+ 58 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageBreak.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+
+class PageBreak
+{
+    /** @var int */
+    private $breakType;
+
+    /** @var string */
+    private $coordinate;
+
+    /** @var int */
+    private $maxColOrRow;
+
+    /** @param array|CellAddress|string $coordinate */
+    public function __construct(int $breakType, $coordinate, int $maxColOrRow = -1)
+    {
+        $coordinate = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
+        $this->breakType = $breakType;
+        $this->coordinate = $coordinate;
+        $this->maxColOrRow = $maxColOrRow;
+    }
+
+    public function getBreakType(): int
+    {
+        return $this->breakType;
+    }
+
+    public function getCoordinate(): string
+    {
+        return $this->coordinate;
+    }
+
+    public function getMaxColOrRow(): int
+    {
+        return $this->maxColOrRow;
+    }
+
+    public function getColumnInt(): int
+    {
+        return Coordinate::indexesFromString($this->coordinate)[0];
+    }
+
+    public function getRow(): int
+    {
+        return Coordinate::indexesFromString($this->coordinate)[1];
+    }
+
+    public function getColumnString(): string
+    {
+        return Coordinate::indexesFromString($this->coordinate)[2];
+    }
+}

+ 585 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php

@@ -0,0 +1,585 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle;
+
+class Table
+{
+    /**
+     * Table Name.
+     *
+     * @var string
+     */
+    private $name;
+
+    /**
+     * Show Header Row.
+     *
+     * @var bool
+     */
+    private $showHeaderRow = true;
+
+    /**
+     * Show Totals Row.
+     *
+     * @var bool
+     */
+    private $showTotalsRow = false;
+
+    /**
+     * Table Range.
+     *
+     * @var string
+     */
+    private $range = '';
+
+    /**
+     * Table Worksheet.
+     *
+     * @var null|Worksheet
+     */
+    private $workSheet;
+
+    /**
+     * Table allow filter.
+     *
+     * @var bool
+     */
+    private $allowFilter = true;
+
+    /**
+     * Table Column.
+     *
+     * @var Table\Column[]
+     */
+    private $columns = [];
+
+    /**
+     * Table Style.
+     *
+     * @var TableStyle
+     */
+    private $style;
+
+    /**
+     * Table AutoFilter.
+     *
+     * @var AutoFilter
+     */
+    private $autoFilter;
+
+    /**
+     * Create a new Table.
+     *
+     * @param AddressRange|array<int>|string $range
+     *            A simple string containing a Cell range like 'A1:E10' is permitted
+     *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
+     *              or an AddressRange object.
+     * @param string $name (e.g. Table1)
+     */
+    public function __construct($range = '', string $name = '')
+    {
+        $this->style = new TableStyle();
+        $this->autoFilter = new AutoFilter($range);
+        $this->setRange($range);
+        $this->setName($name);
+    }
+
+    /**
+     * Get Table name.
+     */
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    /**
+     * Set Table name.
+     *
+     * @throws PhpSpreadsheetException
+     */
+    public function setName(string $name): self
+    {
+        $name = trim($name);
+
+        if (!empty($name)) {
+            if (strlen($name) === 1 && in_array($name, ['C', 'c', 'R', 'r'])) {
+                throw new PhpSpreadsheetException('The table name is invalid');
+            }
+            if (StringHelper::countCharacters($name) > 255) {
+                throw new PhpSpreadsheetException('The table name cannot be longer than 255 characters');
+            }
+            // Check for A1 or R1C1 cell reference notation
+            if (
+                preg_match(Coordinate::A1_COORDINATE_REGEX, $name) ||
+                preg_match('/^R\[?\-?[0-9]*\]?C\[?\-?[0-9]*\]?$/i', $name)
+            ) {
+                throw new PhpSpreadsheetException('The table name can\'t be the same as a cell reference');
+            }
+            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)) {
+                throw new PhpSpreadsheetException('The table name contains invalid characters');
+            }
+
+            $this->checkForDuplicateTableNames($name, $this->workSheet);
+            $this->updateStructuredReferences($name);
+        }
+
+        $this->name = $name;
+
+        return $this;
+    }
+
+    /**
+     * @throws PhpSpreadsheetException
+     */
+    private function checkForDuplicateTableNames(string $name, ?Worksheet $worksheet): void
+    {
+        // Remember that table names are case-insensitive
+        $tableName = StringHelper::strToLower($name);
+
+        if ($worksheet !== null && StringHelper::strToLower($this->name) !== $name) {
+            $spreadsheet = $worksheet->getParentOrThrow();
+
+            foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
+                foreach ($sheet->getTableCollection() as $table) {
+                    if (StringHelper::strToLower($table->getName()) === $tableName && $table != $this) {
+                        throw new PhpSpreadsheetException("Spreadsheet already contains a table named '{$this->name}'");
+                    }
+                }
+            }
+        }
+    }
+
+    private function updateStructuredReferences(string $name): void
+    {
+        if ($this->workSheet === null || $this->name === null || $this->name === '') {
+            return;
+        }
+
+        // Remember that table names are case-insensitive
+        if (StringHelper::strToLower($this->name) !== StringHelper::strToLower($name)) {
+            // We need to check all formula cells that might contain fully-qualified Structured References
+            //    that refer to this table, and update those formulae to reference the new table name
+            $spreadsheet = $this->workSheet->getParentOrThrow();
+            foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
+                $this->updateStructuredReferencesInCells($sheet, $name);
+            }
+            $this->updateStructuredReferencesInNamedFormulae($spreadsheet, $name);
+        }
+    }
+
+    private function updateStructuredReferencesInCells(Worksheet $worksheet, string $newName): void
+    {
+        $pattern = '/' . preg_quote($this->name, '/') . '\[/mui';
+
+        foreach ($worksheet->getCoordinates(false) as $coordinate) {
+            $cell = $worksheet->getCell($coordinate);
+            if ($cell->getDataType() === DataType::TYPE_FORMULA) {
+                $formula = $cell->getValue();
+                if (preg_match($pattern, $formula) === 1) {
+                    $formula = preg_replace($pattern, "{$newName}[", $formula);
+                    $cell->setValueExplicit($formula, DataType::TYPE_FORMULA);
+                }
+            }
+        }
+    }
+
+    private function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $newName): void
+    {
+        $pattern = '/' . preg_quote($this->name, '/') . '\[/mui';
+
+        foreach ($spreadsheet->getNamedFormulae() as $namedFormula) {
+            $formula = $namedFormula->getValue();
+            if (preg_match($pattern, $formula) === 1) {
+                $formula = preg_replace($pattern, "{$newName}[", $formula);
+                $namedFormula->setValue($formula); // @phpstan-ignore-line
+            }
+        }
+    }
+
+    /**
+     * Get show Header Row.
+     */
+    public function getShowHeaderRow(): bool
+    {
+        return $this->showHeaderRow;
+    }
+
+    /**
+     * Set show Header Row.
+     */
+    public function setShowHeaderRow(bool $showHeaderRow): self
+    {
+        $this->showHeaderRow = $showHeaderRow;
+
+        return $this;
+    }
+
+    /**
+     * Get show Totals Row.
+     */
+    public function getShowTotalsRow(): bool
+    {
+        return $this->showTotalsRow;
+    }
+
+    /**
+     * Set show Totals Row.
+     */
+    public function setShowTotalsRow(bool $showTotalsRow): self
+    {
+        $this->showTotalsRow = $showTotalsRow;
+
+        return $this;
+    }
+
+    /**
+     * Get allow filter.
+     * If false, autofiltering is disabled for the table, if true it is enabled.
+     */
+    public function getAllowFilter(): bool
+    {
+        return $this->allowFilter;
+    }
+
+    /**
+     * Set show Autofiltering.
+     * Disabling autofiltering has the same effect as hiding the filter button on all the columns in the table.
+     */
+    public function setAllowFilter(bool $allowFilter): self
+    {
+        $this->allowFilter = $allowFilter;
+
+        return $this;
+    }
+
+    /**
+     * Get Table Range.
+     */
+    public function getRange(): string
+    {
+        return $this->range;
+    }
+
+    /**
+     * Set Table Cell Range.
+     *
+     * @param AddressRange|array<int>|string $range
+     *            A simple string containing a Cell range like 'A1:E10' is permitted
+     *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
+     *              or an AddressRange object.
+     */
+    public function setRange($range = ''): self
+    {
+        // extract coordinate
+        if ($range !== '') {
+            [, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true);
+        }
+        if (empty($range)) {
+            //    Discard all column rules
+            $this->columns = [];
+            $this->range = '';
+
+            return $this;
+        }
+
+        if (strpos($range, ':') === false) {
+            throw new PhpSpreadsheetException('Table must be set on a range of cells.');
+        }
+
+        [$width, $height] = Coordinate::rangeDimension($range);
+        if ($width < 1 || $height < 1) {
+            throw new PhpSpreadsheetException('The table range must be at least 1 column and row');
+        }
+
+        $this->range = $range;
+        $this->autoFilter->setRange($range);
+
+        //    Discard any column rules that are no longer valid within this range
+        [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
+        foreach ($this->columns as $key => $value) {
+            $colIndex = Coordinate::columnIndexFromString($key);
+            if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) {
+                unset($this->columns[$key]);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Set Table Cell Range to max row.
+     */
+    public function setRangeToMaxRow(): self
+    {
+        if ($this->workSheet !== null) {
+            $thisrange = $this->range;
+            $range = (string) preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange);
+            if ($range !== $thisrange) {
+                $this->setRange($range);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Get Table's Worksheet.
+     */
+    public function getWorksheet(): ?Worksheet
+    {
+        return $this->workSheet;
+    }
+
+    /**
+     * Set Table's Worksheet.
+     */
+    public function setWorksheet(?Worksheet $worksheet = null): self
+    {
+        if ($this->name !== '' && $worksheet !== null) {
+            $spreadsheet = $worksheet->getParentOrThrow();
+            $tableName = StringHelper::strToUpper($this->name);
+
+            foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
+                foreach ($sheet->getTableCollection() as $table) {
+                    if (StringHelper::strToUpper($table->getName()) === $tableName) {
+                        throw new PhpSpreadsheetException("Workbook already contains a table named '{$this->name}'");
+                    }
+                }
+            }
+        }
+
+        $this->workSheet = $worksheet;
+        $this->autoFilter->setParent($worksheet);
+
+        return $this;
+    }
+
+    /**
+     * Get all Table Columns.
+     *
+     * @return Table\Column[]
+     */
+    public function getColumns(): array
+    {
+        return $this->columns;
+    }
+
+    /**
+     * Validate that the specified column is in the Table range.
+     *
+     * @param string $column Column name (e.g. A)
+     *
+     * @return int The column offset within the table range
+     */
+    public function isColumnInRange(string $column): int
+    {
+        if (empty($this->range)) {
+            throw new PhpSpreadsheetException('No table range is defined.');
+        }
+
+        $columnIndex = Coordinate::columnIndexFromString($column);
+        [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
+        if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) {
+            throw new PhpSpreadsheetException('Column is outside of current table range.');
+        }
+
+        return $columnIndex - $rangeStart[0];
+    }
+
+    /**
+     * Get a specified Table Column Offset within the defined Table range.
+     *
+     * @param string $column Column name (e.g. A)
+     *
+     * @return int The offset of the specified column within the table range
+     */
+    public function getColumnOffset($column): int
+    {
+        return $this->isColumnInRange($column);
+    }
+
+    /**
+     * Get a specified Table Column.
+     *
+     * @param string $column Column name (e.g. A)
+     */
+    public function getColumn($column): Table\Column
+    {
+        $this->isColumnInRange($column);
+
+        if (!isset($this->columns[$column])) {
+            $this->columns[$column] = new Table\Column($column, $this);
+        }
+
+        return $this->columns[$column];
+    }
+
+    /**
+     * Get a specified Table Column by it's offset.
+     *
+     * @param int $columnOffset Column offset within range (starting from 0)
+     */
+    public function getColumnByOffset($columnOffset): Table\Column
+    {
+        [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
+        $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $columnOffset);
+
+        return $this->getColumn($pColumn);
+    }
+
+    /**
+     * Set Table.
+     *
+     * @param string|Table\Column $columnObjectOrString
+     *            A simple string containing a Column ID like 'A' is permitted
+     */
+    public function setColumn($columnObjectOrString): self
+    {
+        if ((is_string($columnObjectOrString)) && (!empty($columnObjectOrString))) {
+            $column = $columnObjectOrString;
+        } elseif (is_object($columnObjectOrString) && ($columnObjectOrString instanceof Table\Column)) {
+            $column = $columnObjectOrString->getColumnIndex();
+        } else {
+            throw new PhpSpreadsheetException('Column is not within the table range.');
+        }
+        $this->isColumnInRange($column);
+
+        if (is_string($columnObjectOrString)) {
+            $this->columns[$columnObjectOrString] = new Table\Column($columnObjectOrString, $this);
+        } else {
+            $columnObjectOrString->setTable($this);
+            $this->columns[$column] = $columnObjectOrString;
+        }
+        ksort($this->columns);
+
+        return $this;
+    }
+
+    /**
+     * Clear a specified Table Column.
+     *
+     * @param string $column Column name (e.g. A)
+     */
+    public function clearColumn($column): self
+    {
+        $this->isColumnInRange($column);
+
+        if (isset($this->columns[$column])) {
+            unset($this->columns[$column]);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Shift an Table Column Rule to a different column.
+     *
+     * Note: This method bypasses validation of the destination column to ensure it is within this Table range.
+     *        Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value.
+     *        Use with caution.
+     *
+     * @param string $fromColumn Column name (e.g. A)
+     * @param string $toColumn Column name (e.g. B)
+     */
+    public function shiftColumn($fromColumn, $toColumn): self
+    {
+        $fromColumn = strtoupper($fromColumn);
+        $toColumn = strtoupper($toColumn);
+
+        if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) {
+            $this->columns[$fromColumn]->setTable();
+            $this->columns[$fromColumn]->setColumnIndex($toColumn);
+            $this->columns[$toColumn] = $this->columns[$fromColumn];
+            $this->columns[$toColumn]->setTable($this);
+            unset($this->columns[$fromColumn]);
+
+            ksort($this->columns);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Get table Style.
+     */
+    public function getStyle(): Table\TableStyle
+    {
+        return $this->style;
+    }
+
+    /**
+     * Set table Style.
+     */
+    public function setStyle(TableStyle $style): self
+    {
+        $this->style = $style;
+
+        return $this;
+    }
+
+    /**
+     * Get AutoFilter.
+     */
+    public function getAutoFilter(): AutoFilter
+    {
+        return $this->autoFilter;
+    }
+
+    /**
+     * Set AutoFilter.
+     */
+    public function setAutoFilter(AutoFilter $autoFilter): self
+    {
+        $this->autoFilter = $autoFilter;
+
+        return $this;
+    }
+
+    /**
+     * Implement PHP __clone to create a deep clone, not just a shallow copy.
+     */
+    public function __clone()
+    {
+        $vars = get_object_vars($this);
+        foreach ($vars as $key => $value) {
+            if (is_object($value)) {
+                if ($key === 'workSheet') {
+                    //    Detach from worksheet
+                    $this->{$key} = null;
+                } else {
+                    $this->{$key} = clone $value;
+                }
+            } elseif ((is_array($value)) && ($key === 'columns')) {
+                //    The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table objects
+                $this->{$key} = [];
+                foreach ($value as $k => $v) {
+                    $this->{$key}[$k] = clone $v;
+                    // attach the new cloned Column to this new cloned Table object
+                    $this->{$key}[$k]->setTable($this);
+                }
+            } else {
+                $this->{$key} = $value;
+            }
+        }
+    }
+
+    /**
+     * toString method replicates previous behavior by returning the range if object is
+     * referenced as a property of its worksheet.
+     */
+    public function __toString()
+    {
+        return (string) $this->range;
+    }
+}

+ 254 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php

@@ -0,0 +1,254 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet\Table;
+
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\Table;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class Column
+{
+    /**
+     * Table Column Index.
+     *
+     * @var string
+     */
+    private $columnIndex = '';
+
+    /**
+     * Show Filter Button.
+     *
+     * @var bool
+     */
+    private $showFilterButton = true;
+
+    /**
+     * Total Row Label.
+     *
+     * @var string
+     */
+    private $totalsRowLabel;
+
+    /**
+     * Total Row Function.
+     *
+     * @var string
+     */
+    private $totalsRowFunction;
+
+    /**
+     * Total Row Formula.
+     *
+     * @var string
+     */
+    private $totalsRowFormula;
+
+    /**
+     * Column Formula.
+     *
+     * @var string
+     */
+    private $columnFormula;
+
+    /**
+     * Table.
+     *
+     * @var null|Table
+     */
+    private $table;
+
+    /**
+     * Create a new Column.
+     *
+     * @param string $column Column (e.g. A)
+     * @param Table $table Table for this column
+     */
+    public function __construct($column, ?Table $table = null)
+    {
+        $this->columnIndex = $column;
+        $this->table = $table;
+    }
+
+    /**
+     * Get Table column index as string eg: 'A'.
+     */
+    public function getColumnIndex(): string
+    {
+        return $this->columnIndex;
+    }
+
+    /**
+     * Set Table column index as string eg: 'A'.
+     *
+     * @param string $column Column (e.g. A)
+     */
+    public function setColumnIndex($column): self
+    {
+        // Uppercase coordinate
+        $column = strtoupper($column);
+        if ($this->table !== null) {
+            $this->table->isColumnInRange($column);
+        }
+
+        $this->columnIndex = $column;
+
+        return $this;
+    }
+
+    /**
+     * Get show Filter Button.
+     */
+    public function getShowFilterButton(): bool
+    {
+        return $this->showFilterButton;
+    }
+
+    /**
+     * Set show Filter Button.
+     */
+    public function setShowFilterButton(bool $showFilterButton): self
+    {
+        $this->showFilterButton = $showFilterButton;
+
+        return $this;
+    }
+
+    /**
+     * Get total Row Label.
+     */
+    public function getTotalsRowLabel(): ?string
+    {
+        return $this->totalsRowLabel;
+    }
+
+    /**
+     * Set total Row Label.
+     */
+    public function setTotalsRowLabel(string $totalsRowLabel): self
+    {
+        $this->totalsRowLabel = $totalsRowLabel;
+
+        return $this;
+    }
+
+    /**
+     * Get total Row Function.
+     */
+    public function getTotalsRowFunction(): ?string
+    {
+        return $this->totalsRowFunction;
+    }
+
+    /**
+     * Set total Row Function.
+     */
+    public function setTotalsRowFunction(string $totalsRowFunction): self
+    {
+        $this->totalsRowFunction = $totalsRowFunction;
+
+        return $this;
+    }
+
+    /**
+     * Get total Row Formula.
+     */
+    public function getTotalsRowFormula(): ?string
+    {
+        return $this->totalsRowFormula;
+    }
+
+    /**
+     * Set total Row Formula.
+     */
+    public function setTotalsRowFormula(string $totalsRowFormula): self
+    {
+        $this->totalsRowFormula = $totalsRowFormula;
+
+        return $this;
+    }
+
+    /**
+     * Get column Formula.
+     */
+    public function getColumnFormula(): ?string
+    {
+        return $this->columnFormula;
+    }
+
+    /**
+     * Set column Formula.
+     */
+    public function setColumnFormula(string $columnFormula): self
+    {
+        $this->columnFormula = $columnFormula;
+
+        return $this;
+    }
+
+    /**
+     * Get this Column's Table.
+     */
+    public function getTable(): ?Table
+    {
+        return $this->table;
+    }
+
+    /**
+     * Set this Column's Table.
+     */
+    public function setTable(?Table $table = null): self
+    {
+        $this->table = $table;
+
+        return $this;
+    }
+
+    public static function updateStructuredReferences(?Worksheet $workSheet, ?string $oldTitle, ?string $newTitle): void
+    {
+        if ($workSheet === null || $oldTitle === null || $oldTitle === '' || $newTitle === null) {
+            return;
+        }
+
+        // Remember that table headings are case-insensitive
+        if (StringHelper::strToLower($oldTitle) !== StringHelper::strToLower($newTitle)) {
+            // We need to check all formula cells that might contain Structured References that refer
+            //    to this column, and update those formulae to reference the new column text
+            $spreadsheet = $workSheet->getParentOrThrow();
+            foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
+                self::updateStructuredReferencesInCells($sheet, $oldTitle, $newTitle);
+            }
+            self::updateStructuredReferencesInNamedFormulae($spreadsheet, $oldTitle, $newTitle);
+        }
+    }
+
+    private static function updateStructuredReferencesInCells(Worksheet $worksheet, string $oldTitle, string $newTitle): void
+    {
+        $pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui';
+
+        foreach ($worksheet->getCoordinates(false) as $coordinate) {
+            $cell = $worksheet->getCell($coordinate);
+            if ($cell->getDataType() === DataType::TYPE_FORMULA) {
+                $formula = $cell->getValue();
+                if (preg_match($pattern, $formula) === 1) {
+                    $formula = preg_replace($pattern, "[$1{$newTitle}]", $formula);
+                    $cell->setValueExplicit($formula, DataType::TYPE_FORMULA);
+                }
+            }
+        }
+    }
+
+    private static function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $oldTitle, string $newTitle): void
+    {
+        $pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui';
+
+        foreach ($spreadsheet->getNamedFormulae() as $namedFormula) {
+            $formula = $namedFormula->getValue();
+            if (preg_match($pattern, $formula) === 1) {
+                $formula = preg_replace($pattern, "[$1{$newTitle}]", $formula);
+                $namedFormula->setValue($formula); // @phpstan-ignore-line
+            }
+        }
+    }
+}

+ 230 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/TableStyle.php

@@ -0,0 +1,230 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet\Table;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\Table;
+
+class TableStyle
+{
+    const TABLE_STYLE_NONE = '';
+    const TABLE_STYLE_LIGHT1 = 'TableStyleLight1';
+    const TABLE_STYLE_LIGHT2 = 'TableStyleLight2';
+    const TABLE_STYLE_LIGHT3 = 'TableStyleLight3';
+    const TABLE_STYLE_LIGHT4 = 'TableStyleLight4';
+    const TABLE_STYLE_LIGHT5 = 'TableStyleLight5';
+    const TABLE_STYLE_LIGHT6 = 'TableStyleLight6';
+    const TABLE_STYLE_LIGHT7 = 'TableStyleLight7';
+    const TABLE_STYLE_LIGHT8 = 'TableStyleLight8';
+    const TABLE_STYLE_LIGHT9 = 'TableStyleLight9';
+    const TABLE_STYLE_LIGHT10 = 'TableStyleLight10';
+    const TABLE_STYLE_LIGHT11 = 'TableStyleLight11';
+    const TABLE_STYLE_LIGHT12 = 'TableStyleLight12';
+    const TABLE_STYLE_LIGHT13 = 'TableStyleLight13';
+    const TABLE_STYLE_LIGHT14 = 'TableStyleLight14';
+    const TABLE_STYLE_LIGHT15 = 'TableStyleLight15';
+    const TABLE_STYLE_LIGHT16 = 'TableStyleLight16';
+    const TABLE_STYLE_LIGHT17 = 'TableStyleLight17';
+    const TABLE_STYLE_LIGHT18 = 'TableStyleLight18';
+    const TABLE_STYLE_LIGHT19 = 'TableStyleLight19';
+    const TABLE_STYLE_LIGHT20 = 'TableStyleLight20';
+    const TABLE_STYLE_LIGHT21 = 'TableStyleLight21';
+    const TABLE_STYLE_MEDIUM1 = 'TableStyleMedium1';
+    const TABLE_STYLE_MEDIUM2 = 'TableStyleMedium2';
+    const TABLE_STYLE_MEDIUM3 = 'TableStyleMedium3';
+    const TABLE_STYLE_MEDIUM4 = 'TableStyleMedium4';
+    const TABLE_STYLE_MEDIUM5 = 'TableStyleMedium5';
+    const TABLE_STYLE_MEDIUM6 = 'TableStyleMedium6';
+    const TABLE_STYLE_MEDIUM7 = 'TableStyleMedium7';
+    const TABLE_STYLE_MEDIUM8 = 'TableStyleMedium8';
+    const TABLE_STYLE_MEDIUM9 = 'TableStyleMedium9';
+    const TABLE_STYLE_MEDIUM10 = 'TableStyleMedium10';
+    const TABLE_STYLE_MEDIUM11 = 'TableStyleMedium11';
+    const TABLE_STYLE_MEDIUM12 = 'TableStyleMedium12';
+    const TABLE_STYLE_MEDIUM13 = 'TableStyleMedium13';
+    const TABLE_STYLE_MEDIUM14 = 'TableStyleMedium14';
+    const TABLE_STYLE_MEDIUM15 = 'TableStyleMedium15';
+    const TABLE_STYLE_MEDIUM16 = 'TableStyleMedium16';
+    const TABLE_STYLE_MEDIUM17 = 'TableStyleMedium17';
+    const TABLE_STYLE_MEDIUM18 = 'TableStyleMedium18';
+    const TABLE_STYLE_MEDIUM19 = 'TableStyleMedium19';
+    const TABLE_STYLE_MEDIUM20 = 'TableStyleMedium20';
+    const TABLE_STYLE_MEDIUM21 = 'TableStyleMedium21';
+    const TABLE_STYLE_MEDIUM22 = 'TableStyleMedium22';
+    const TABLE_STYLE_MEDIUM23 = 'TableStyleMedium23';
+    const TABLE_STYLE_MEDIUM24 = 'TableStyleMedium24';
+    const TABLE_STYLE_MEDIUM25 = 'TableStyleMedium25';
+    const TABLE_STYLE_MEDIUM26 = 'TableStyleMedium26';
+    const TABLE_STYLE_MEDIUM27 = 'TableStyleMedium27';
+    const TABLE_STYLE_MEDIUM28 = 'TableStyleMedium28';
+    const TABLE_STYLE_DARK1 = 'TableStyleDark1';
+    const TABLE_STYLE_DARK2 = 'TableStyleDark2';
+    const TABLE_STYLE_DARK3 = 'TableStyleDark3';
+    const TABLE_STYLE_DARK4 = 'TableStyleDark4';
+    const TABLE_STYLE_DARK5 = 'TableStyleDark5';
+    const TABLE_STYLE_DARK6 = 'TableStyleDark6';
+    const TABLE_STYLE_DARK7 = 'TableStyleDark7';
+    const TABLE_STYLE_DARK8 = 'TableStyleDark8';
+    const TABLE_STYLE_DARK9 = 'TableStyleDark9';
+    const TABLE_STYLE_DARK10 = 'TableStyleDark10';
+    const TABLE_STYLE_DARK11 = 'TableStyleDark11';
+
+    /**
+     * Theme.
+     *
+     * @var string
+     */
+    private $theme;
+
+    /**
+     * Show First Column.
+     *
+     * @var bool
+     */
+    private $showFirstColumn = false;
+
+    /**
+     * Show Last Column.
+     *
+     * @var bool
+     */
+    private $showLastColumn = false;
+
+    /**
+     * Show Row Stripes.
+     *
+     * @var bool
+     */
+    private $showRowStripes = false;
+
+    /**
+     * Show Column Stripes.
+     *
+     * @var bool
+     */
+    private $showColumnStripes = false;
+
+    /**
+     * Table.
+     *
+     * @var null|Table
+     */
+    private $table;
+
+    /**
+     * Create a new Table Style.
+     *
+     * @param string $theme (e.g. TableStyle::TABLE_STYLE_MEDIUM2)
+     */
+    public function __construct(string $theme = self::TABLE_STYLE_MEDIUM2)
+    {
+        $this->theme = $theme;
+    }
+
+    /**
+     * Get theme.
+     */
+    public function getTheme(): string
+    {
+        return $this->theme;
+    }
+
+    /**
+     * Set theme.
+     */
+    public function setTheme(string $theme): self
+    {
+        $this->theme = $theme;
+
+        return $this;
+    }
+
+    /**
+     * Get show First Column.
+     */
+    public function getShowFirstColumn(): bool
+    {
+        return $this->showFirstColumn;
+    }
+
+    /**
+     * Set show First Column.
+     */
+    public function setShowFirstColumn(bool $showFirstColumn): self
+    {
+        $this->showFirstColumn = $showFirstColumn;
+
+        return $this;
+    }
+
+    /**
+     * Get show Last Column.
+     */
+    public function getShowLastColumn(): bool
+    {
+        return $this->showLastColumn;
+    }
+
+    /**
+     * Set show Last Column.
+     */
+    public function setShowLastColumn(bool $showLastColumn): self
+    {
+        $this->showLastColumn = $showLastColumn;
+
+        return $this;
+    }
+
+    /**
+     * Get show Row Stripes.
+     */
+    public function getShowRowStripes(): bool
+    {
+        return $this->showRowStripes;
+    }
+
+    /**
+     * Set show Row Stripes.
+     */
+    public function setShowRowStripes(bool $showRowStripes): self
+    {
+        $this->showRowStripes = $showRowStripes;
+
+        return $this;
+    }
+
+    /**
+     * Get show Column Stripes.
+     */
+    public function getShowColumnStripes(): bool
+    {
+        return $this->showColumnStripes;
+    }
+
+    /**
+     * Set show Column Stripes.
+     */
+    public function setShowColumnStripes(bool $showColumnStripes): self
+    {
+        $this->showColumnStripes = $showColumnStripes;
+
+        return $this;
+    }
+
+    /**
+     * Get this Style's Table.
+     */
+    public function getTable(): ?Table
+    {
+        return $this->table;
+    }
+
+    /**
+     * Set this Style's Table.
+     */
+    public function setTable(?Table $table = null): self
+    {
+        $this->table = $table;
+
+        return $this;
+    }
+}

+ 118 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php

@@ -0,0 +1,118 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
+use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
+use PhpOffice\PhpSpreadsheet\Cell\CellRange;
+use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
+
+class Validations
+{
+    /**
+     * Validate a cell address.
+     *
+     * @param null|array<int>|CellAddress|string $cellAddress Coordinate of the cell as a string, eg: 'C5';
+     *               or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
+     */
+    public static function validateCellAddress($cellAddress): string
+    {
+        if (is_string($cellAddress)) {
+            [$worksheet, $address] = Worksheet::extractSheetTitle($cellAddress, true);
+//            if (!empty($worksheet) && $worksheet !== $this->getTitle()) {
+//                throw new Exception('Reference is not for this worksheet');
+//            }
+
+            return empty($worksheet) ? strtoupper("$address") : $worksheet . '!' . strtoupper("$address");
+        }
+
+        if (is_array($cellAddress)) {
+            $cellAddress = CellAddress::fromColumnRowArray($cellAddress);
+        }
+
+        return (string) $cellAddress;
+    }
+
+    /**
+     * Validate a cell address or cell range.
+     *
+     * @param AddressRange|array<int>|CellAddress|int|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
+     *               or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
+     *               or as a CellAddress or AddressRange object.
+     */
+    public static function validateCellOrCellRange($cellRange): string
+    {
+        if (is_string($cellRange) || is_numeric($cellRange)) {
+            // Convert a single column reference like 'A' to 'A:A',
+            //    a single row reference like '1' to '1:1'
+            $cellRange = (string) preg_replace('/^([A-Z]+|\d+)$/', '${1}:${1}', (string) $cellRange);
+        } elseif (is_object($cellRange) && $cellRange instanceof CellAddress) {
+            $cellRange = new CellRange($cellRange, $cellRange);
+        }
+
+        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.
+     *
+     * @param AddressRange|array<int>|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
+     *               or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
+     *               or as an AddressRange object.
+     */
+    public static function validateCellRange($cellRange): string
+    {
+        if (is_string($cellRange)) {
+            [$worksheet, $addressRange] = Worksheet::extractSheetTitle($cellRange, true);
+
+            // 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+)$/'],
+                [self::SETMAXROW, self::SETMAXCOL],
+                $addressRange
+            );
+
+            return empty($worksheet) ? strtoupper($addressRange) : $worksheet . '!' . strtoupper($addressRange);
+        }
+
+        if (is_array($cellRange)) {
+            switch (count($cellRange)) {
+                case 2:
+                    $from = [$cellRange[0], $cellRange[1]];
+                    $to = [$cellRange[0], $cellRange[1]];
+
+                    break;
+                case 4:
+                    $from = [$cellRange[0], $cellRange[1]];
+                    $to = [$cellRange[2], $cellRange[3]];
+
+                    break;
+                default:
+                    throw new SpreadsheetException('CellRange array length must be 2 or 4');
+            }
+            $cellRange = new CellRange(CellAddress::fromColumnRowArray($from), CellAddress::fromColumnRowArray($to));
+        }
+
+        return (string) $cellRange;
+    }
+
+    public static function definedNameToCoordinate(string $coordinate, Worksheet $worksheet): string
+    {
+        // Uppercase coordinate
+        $coordinate = strtoupper($coordinate);
+        // Eliminate leading equal sign
+        $testCoordinate = (string) preg_replace('/^=/', '', $coordinate);
+        $defined = $worksheet->getParentOrThrow()->getDefinedName($testCoordinate, $worksheet);
+        if ($defined !== null) {
+            if ($defined->getWorksheet() === $worksheet && !$defined->isFormula()) {
+                $coordinate = (string) preg_replace('/^=/', '', $defined->getValue());
+            }
+        }
+
+        return $coordinate;
+    }
+}

+ 76 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/ConditionalHelper.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
+
+class ConditionalHelper
+{
+    /**
+     * Formula parser.
+     *
+     * @var Parser
+     */
+    protected $parser;
+
+    /**
+     * @var mixed
+     */
+    protected $condition;
+
+    /**
+     * @var string
+     */
+    protected $cellRange;
+
+    /**
+     * @var null|string
+     */
+    protected $tokens;
+
+    /**
+     * @var int
+     */
+    protected $size;
+
+    public function __construct(Parser $parser)
+    {
+        $this->parser = $parser;
+    }
+
+    /**
+     * @param mixed $condition
+     */
+    public function processCondition($condition, string $cellRange): void
+    {
+        $this->condition = $condition;
+        $this->cellRange = $cellRange;
+
+        if (is_int($condition) || is_float($condition)) {
+            $this->size = ($condition <= 65535 ? 3 : 0x0000);
+            $this->tokens = pack('Cv', 0x1E, $condition);
+        } else {
+            try {
+                $formula = Wizard\WizardAbstract::reverseAdjustCellRef((string) $condition, $cellRange);
+                $this->parser->parse($formula);
+                $this->tokens = $this->parser->toReversePolish();
+                $this->size = strlen($this->tokens ?? '');
+            } catch (PhpSpreadsheetException $e) {
+                // In the event of a parser error with a formula value, we set the expression to ptgInt + 0
+                $this->tokens = pack('Cv', 0x1E, 0);
+                $this->size = 3;
+            }
+        }
+    }
+
+    public function tokens(): ?string
+    {
+        return $this->tokens;
+    }
+
+    public function size(): int
+    {
+        return $this->size;
+    }
+}

+ 125 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
+use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as ActualWorksheet;
+
+class AutoFilter extends WriterPart
+{
+    /**
+     * Write AutoFilter.
+     */
+    public static function writeAutoFilter(XMLWriter $objWriter, ActualWorksheet $worksheet): void
+    {
+        $autoFilterRange = $worksheet->getAutoFilter()->getRange();
+        if (!empty($autoFilterRange)) {
+            // autoFilter
+            $objWriter->startElement('autoFilter');
+
+            // Strip any worksheet reference from the filter coordinates
+            $range = Coordinate::splitRange($autoFilterRange);
+            $range = $range[0];
+            //    Strip any worksheet ref
+            [$ws, $range[0]] = ActualWorksheet::extractSheetTitle($range[0], true);
+            $range = implode(':', $range);
+
+            $objWriter->writeAttribute('ref', str_replace('$', '', $range));
+
+            $columns = $worksheet->getAutoFilter()->getColumns();
+            if (count($columns) > 0) {
+                foreach ($columns as $columnID => $column) {
+                    $colId = $worksheet->getAutoFilter()->getColumnOffset($columnID);
+                    self::writeAutoFilterColumn($objWriter, $column, $colId);
+                }
+            }
+            $objWriter->endElement();
+        }
+    }
+
+    /**
+     * Write AutoFilter's filterColumn.
+     */
+    public static function writeAutoFilterColumn(XMLWriter $objWriter, Column $column, int $colId): void
+    {
+        $rules = $column->getRules();
+        if (count($rules) > 0) {
+            $objWriter->startElement('filterColumn');
+            $objWriter->writeAttribute('colId', "$colId");
+
+            $objWriter->startElement($column->getFilterType());
+            if ($column->getJoin() == Column::AUTOFILTER_COLUMN_JOIN_AND) {
+                $objWriter->writeAttribute('and', '1');
+            }
+
+            foreach ($rules as $rule) {
+                self::writeAutoFilterColumnRule($column, $rule, $objWriter);
+            }
+
+            $objWriter->endElement();
+
+            $objWriter->endElement();
+        }
+    }
+
+    /**
+     * Write AutoFilter's filterColumn Rule.
+     */
+    private static function writeAutoFilterColumnRule(Column $column, Rule $rule, XMLWriter $objWriter): void
+    {
+        if (
+            ($column->getFilterType() === Column::AUTOFILTER_FILTERTYPE_FILTER) &&
+            ($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_EQUAL) &&
+            ($rule->getValue() === '')
+        ) {
+            //    Filter rule for Blanks
+            $objWriter->writeAttribute('blank', '1');
+        } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER) {
+            //    Dynamic Filter Rule
+            $objWriter->writeAttribute('type', $rule->getGrouping());
+            $val = $column->getAttribute('val');
+            if ($val !== null) {
+                $objWriter->writeAttribute('val', "$val");
+            }
+            $maxVal = $column->getAttribute('maxVal');
+            if ($maxVal !== null) {
+                $objWriter->writeAttribute('maxVal', "$maxVal");
+            }
+        } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_TOPTENFILTER) {
+            //    Top 10 Filter Rule
+            $ruleValue = $rule->getValue();
+            if (!is_array($ruleValue)) {
+                $objWriter->writeAttribute('val', "$ruleValue");
+            }
+            $objWriter->writeAttribute('percent', (($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) ? '1' : '0'));
+            $objWriter->writeAttribute('top', (($rule->getGrouping() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) ? '1' : '0'));
+        } else {
+            //    Filter, DateGroupItem or CustomFilter
+            $objWriter->startElement($rule->getRuleType());
+
+            if ($rule->getOperator() !== Rule::AUTOFILTER_COLUMN_RULE_EQUAL) {
+                $objWriter->writeAttribute('operator', $rule->getOperator());
+            }
+            if ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DATEGROUP) {
+                // Date Group filters
+                $ruleValue = $rule->getValue();
+                if (is_array($ruleValue)) {
+                    foreach ($ruleValue as $key => $value) {
+                        $objWriter->writeAttribute($key, "$value");
+                    }
+                }
+                $objWriter->writeAttribute('dateTimeGrouping', $rule->getGrouping());
+            } else {
+                $ruleValue = $rule->getValue();
+                if (!is_array($ruleValue)) {
+                    $objWriter->writeAttribute('val', "$ruleValue");
+                }
+            }
+
+            $objWriter->endElement();
+        }
+    }
+}

+ 194 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php

@@ -0,0 +1,194 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+class FunctionPrefix
+{
+    const XLFNREGEXP = '/(?:_xlfn\.)?((?:_xlws\.)?\b('
+            // functions added with Excel 2010
+        . 'beta[.]dist'
+        . '|beta[.]inv'
+        . '|binom[.]dist'
+        . '|binom[.]inv'
+        . '|ceiling[.]precise'
+        . '|chisq[.]dist'
+        . '|chisq[.]dist[.]rt'
+        . '|chisq[.]inv'
+        . '|chisq[.]inv[.]rt'
+        . '|chisq[.]test'
+        . '|confidence[.]norm'
+        . '|confidence[.]t'
+        . '|covariance[.]p'
+        . '|covariance[.]s'
+        . '|erf[.]precise'
+        . '|erfc[.]precise'
+        . '|expon[.]dist'
+        . '|f[.]dist'
+        . '|f[.]dist[.]rt'
+        . '|f[.]inv'
+        . '|f[.]inv[.]rt'
+        . '|f[.]test'
+        . '|floor[.]precise'
+        . '|gamma[.]dist'
+        . '|gamma[.]inv'
+        . '|gammaln[.]precise'
+        . '|lognorm[.]dist'
+        . '|lognorm[.]inv'
+        . '|mode[.]mult'
+        . '|mode[.]sngl'
+        . '|negbinom[.]dist'
+        . '|networkdays[.]intl'
+        . '|norm[.]dist'
+        . '|norm[.]inv'
+        . '|norm[.]s[.]dist'
+        . '|norm[.]s[.]inv'
+        . '|percentile[.]exc'
+        . '|percentile[.]inc'
+        . '|percentrank[.]exc'
+        . '|percentrank[.]inc'
+        . '|poisson[.]dist'
+        . '|quartile[.]exc'
+        . '|quartile[.]inc'
+        . '|rank[.]avg'
+        . '|rank[.]eq'
+        . '|stdev[.]p'
+        . '|stdev[.]s'
+        . '|t[.]dist'
+        . '|t[.]dist[.]2t'
+        . '|t[.]dist[.]rt'
+        . '|t[.]inv'
+        . '|t[.]inv[.]2t'
+        . '|t[.]test'
+        . '|var[.]p'
+        . '|var[.]s'
+        . '|weibull[.]dist'
+        . '|z[.]test'
+        // functions added with Excel 2013
+        . '|acot'
+        . '|acoth'
+        . '|arabic'
+        . '|averageifs'
+        . '|binom[.]dist[.]range'
+        . '|bitand'
+        . '|bitlshift'
+        . '|bitor'
+        . '|bitrshift'
+        . '|bitxor'
+        . '|ceiling[.]math'
+        . '|combina'
+        . '|cot'
+        . '|coth'
+        . '|csc'
+        . '|csch'
+        . '|days'
+        . '|dbcs'
+        . '|decimal'
+        . '|encodeurl'
+        . '|filterxml'
+        . '|floor[.]math'
+        . '|formulatext'
+        . '|gamma'
+        . '|gauss'
+        . '|ifna'
+        . '|imcosh'
+        . '|imcot'
+        . '|imcsc'
+        . '|imcsch'
+        . '|imsec'
+        . '|imsech'
+        . '|imsinh'
+        . '|imtan'
+        . '|isformula'
+        . '|iso[.]ceiling'
+        . '|isoweeknum'
+        . '|munit'
+        . '|numbervalue'
+        . '|pduration'
+        . '|permutationa'
+        . '|phi'
+        . '|rri'
+        . '|sec'
+        . '|sech'
+        . '|sheet'
+        . '|sheets'
+        . '|skew[.]p'
+        . '|unichar'
+        . '|unicode'
+        . '|webservice'
+        . '|xor'
+        // functions added with Excel 2016
+        . '|forecast[.]et2'
+        . '|forecast[.]ets[.]confint'
+        . '|forecast[.]ets[.]seasonality'
+        . '|forecast[.]ets[.]stat'
+        . '|forecast[.]linear'
+        . '|switch'
+        // functions added with Excel 2019
+        . '|concat'
+        . '|countifs'
+        . '|ifs'
+        . '|maxifs'
+        . '|minifs'
+        . '|sumifs'
+        . '|textjoin'
+        // functions added with Excel 365
+        . '|filter'
+        . '|randarray'
+        . '|anchorarray'
+        . '|sequence'
+        . '|sort'
+        . '|sortby'
+        . '|unique'
+        . '|xlookup'
+        . '|xmatch'
+        . '|arraytotext'
+        . '|call'
+        . '|let'
+        . '|lambda'
+        . '|single'
+        . '|register[.]id'
+        . '|textafter'
+        . '|textbefore'
+        . '|textsplit'
+        . '|valuetotext'
+        . '))\s*\(/Umui';
+
+    const XLWSREGEXP = '/(?<!_xlws\.)('
+        // functions added with Excel 365
+        . 'filter'
+        . '|sort'
+        . ')\s*\(/mui';
+
+    /**
+     * Prefix function name in string with _xlfn. where required.
+     */
+    protected static function addXlfnPrefix(string $functionString): string
+    {
+        return (string) preg_replace(self::XLFNREGEXP, '_xlfn.$1(', $functionString);
+    }
+
+    /**
+     * Prefix function name in string with _xlws. where required.
+     */
+    protected static function addXlwsPrefix(string $functionString): string
+    {
+        return (string) preg_replace(self::XLWSREGEXP, '_xlws.$1(', $functionString);
+    }
+
+    /**
+     * Prefix function name in string with _xlfn. where required.
+     */
+    public static function addFunctionPrefix(string $functionString): string
+    {
+        return self::addXlwsPrefix(self::addXlfnPrefix($functionString));
+    }
+
+    /**
+     * Prefix function name in string with _xlfn. where required.
+     * Leading character, expected to be equals sign, is stripped.
+     */
+    public static function addFunctionPrefixStripEquals(string $functionString): string
+    {
+        return self::addFunctionPrefix(substr($functionString, 1));
+    }
+}

+ 115 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Table.php

@@ -0,0 +1,115 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Worksheet\Table as WorksheetTable;
+
+class Table extends WriterPart
+{
+    /**
+     * Write Table to XML format.
+     *
+     * @param int $tableRef Table ID
+     *
+     * @return string XML Output
+     */
+    public function writeTable(WorksheetTable $table, $tableRef): string
+    {
+        // Create XML writer
+        $objWriter = null;
+        if ($this->getParentWriter()->getUseDiskCaching()) {
+            $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+        } else {
+            $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+        }
+
+        // XML header
+        $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+        // Table
+        $name = 'Table' . $tableRef;
+        $range = $table->getRange();
+
+        $objWriter->startElement('table');
+        $objWriter->writeAttribute('xml:space', 'preserve');
+        $objWriter->writeAttribute('xmlns', Namespaces::MAIN);
+        $objWriter->writeAttribute('id', (string) $tableRef);
+        $objWriter->writeAttribute('name', $name);
+        $objWriter->writeAttribute('displayName', $table->getName() ?: $name);
+        $objWriter->writeAttribute('ref', $range);
+        $objWriter->writeAttribute('headerRowCount', $table->getShowHeaderRow() ? '1' : '0');
+        $objWriter->writeAttribute('totalsRowCount', $table->getShowTotalsRow() ? '1' : '0');
+
+        // Table Boundaries
+        [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($table->getRange());
+
+        // Table Auto Filter
+        if ($table->getShowHeaderRow() && $table->getAllowFilter() === true) {
+            $objWriter->startElement('autoFilter');
+            $objWriter->writeAttribute('ref', $range);
+            $objWriter->endElement();
+            foreach (range($rangeStart[0], $rangeEnd[0]) as $offset => $columnIndex) {
+                $column = $table->getColumnByOffset($offset);
+
+                if (!$column->getShowFilterButton()) {
+                    $objWriter->startElement('filterColumn');
+                    $objWriter->writeAttribute('colId', (string) $offset);
+                    $objWriter->writeAttribute('hiddenButton', '1');
+                    $objWriter->endElement();
+                } else {
+                    $column = $table->getAutoFilter()->getColumnByOffset($offset);
+                    AutoFilter::writeAutoFilterColumn($objWriter, $column, $offset);
+                }
+            }
+        }
+
+        // Table Columns
+        $objWriter->startElement('tableColumns');
+        $objWriter->writeAttribute('count', (string) ($rangeEnd[0] - $rangeStart[0] + 1));
+        foreach (range($rangeStart[0], $rangeEnd[0]) as $offset => $columnIndex) {
+            $worksheet = $table->getWorksheet();
+            if (!$worksheet) {
+                continue;
+            }
+
+            $column = $table->getColumnByOffset($offset);
+            $cell = $worksheet->getCell([$columnIndex, $rangeStart[1]]);
+
+            $objWriter->startElement('tableColumn');
+            $objWriter->writeAttribute('id', (string) ($offset + 1));
+            $objWriter->writeAttribute('name', $table->getShowHeaderRow() ? $cell->getValue() : 'Column' . ($offset + 1));
+
+            if ($table->getShowTotalsRow()) {
+                if ($column->getTotalsRowLabel()) {
+                    $objWriter->writeAttribute('totalsRowLabel', $column->getTotalsRowLabel());
+                }
+                if ($column->getTotalsRowFunction()) {
+                    $objWriter->writeAttribute('totalsRowFunction', $column->getTotalsRowFunction());
+                }
+            }
+            if ($column->getColumnFormula()) {
+                $objWriter->writeElement('calculatedColumnFormula', $column->getColumnFormula());
+            }
+
+            $objWriter->endElement();
+        }
+        $objWriter->endElement();
+
+        // Table Styles
+        $objWriter->startElement('tableStyleInfo');
+        $objWriter->writeAttribute('name', $table->getStyle()->getTheme());
+        $objWriter->writeAttribute('showFirstColumn', $table->getStyle()->getShowFirstColumn() ? '1' : '0');
+        $objWriter->writeAttribute('showLastColumn', $table->getStyle()->getShowLastColumn() ? '1' : '0');
+        $objWriter->writeAttribute('showRowStripes', $table->getStyle()->getShowRowStripes() ? '1' : '0');
+        $objWriter->writeAttribute('showColumnStripes', $table->getStyle()->getShowColumnStripes() ? '1' : '0');
+        $objWriter->endElement();
+
+        $objWriter->endElement();
+
+        // Return
+        return $objWriter->getData();
+    }
+}

+ 17 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream0.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use ZipStream\Option\Archive;
+use ZipStream\ZipStream;
+
+class ZipStream0
+{
+    /**
+     * @param resource $fileHandle
+     */
+    public static function newZipStream($fileHandle): ZipStream
+    {
+        return class_exists(Archive::class) ? ZipStream2::newZipStream($fileHandle) : ZipStream3::newZipStream($fileHandle);
+    }
+}

+ 21 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream2.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use ZipStream\Option\Archive;
+use ZipStream\ZipStream;
+
+class ZipStream2
+{
+    /**
+     * @param resource $fileHandle
+     */
+    public static function newZipStream($fileHandle): ZipStream
+    {
+        $options = new Archive();
+        $options->setEnableZip64(false);
+        $options->setOutputStream($fileHandle);
+
+        return new ZipStream(null, $options);
+    }
+}

+ 22 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream3.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use ZipStream\Option\Archive;
+use ZipStream\ZipStream;
+
+class ZipStream3
+{
+    /**
+     * @param resource $fileHandle
+     */
+    public static function newZipStream($fileHandle): ZipStream
+    {
+        return new ZipStream(
+            enableZip64: false,
+            outputStream: $fileHandle,
+            sendHttpHeaders: false,
+            defaultEnableZeroHeader: false,
+        );
+    }
+}